一、为什么Laravel必须配置SSL
HTTPS不仅是安全需求,更是现代Web的硬性要求:
- 浏览器强制:Chrome/Firefox对HTTP网站标记”不安全”
- SEO排名:Google明确将HTTPS作为排名因素
- API要求:许多第三方API(支付、OAuth等)强制HTTPS
- 安全传输:防止登录凭据、会话Cookie被窃取
- HSTS预加载:现代Web安全标准要求
二、SSL证书类型对比
| 类型 | 验证级别 | 签发速度 | 费用 | 适用场景 |
|---|---|---|---|---|
| Let’s Encrypt (DV) | 域名验证 | 数分钟 | 免费 | 个人/中小型站点 |
| 商业DV证书 | 域名验证 | 数分钟 | $10-50/年 | 需要保险的商业站点 |
| OV证书 | 组织验证 | 1-3天 | $100-300/年 | 企业官网 |
| EV证书 | 扩展验证 | 3-7天 | $200-800/年 | 金融/电商平台 |
| 通配符证书 | 域名验证 | 数分钟 | 免费(LE) | 多子域名站点 |
三、前置条件
3.1 环境要求
- Debian 11/12服务器
- 已安装Nginx
- 已部署Laravel项目
- 域名已解析到服务器IP
- 80和443端口已开放
3.2 验证域名解析
# 检查域名是否解析到服务器
dig yourdomain.com +short
# 应返回你的服务器IP
# 检查端口是否开放
sudo ss -tulnp | grep -E ':(80|443)'
3.3 确认Nginx已配置HTTP
确保Nginx已正确监听80端口并可以访问Laravel站点:
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/myapp/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
四、使用Certbot获取SSL证书
4.1 安装Certbot
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
4.2 获取单域名证书
sudo certbot --nginx -d yourdomain.com
4.3 获取多域名证书
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
4.4 获取通配符证书(需DNS验证)
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.yourdomain.com" -d yourdomain.com
执行后Certbot会给出DNS TXT记录要求,需要在域名管理后台添加:
_acme-challenge.yourdomain.com TXT "验证码字符串"
验证DNS记录生效后按回车继续。
4.5 使用DNS插件自动续期(Cloudflare示例)
# 安装Cloudflare DNS插件
sudo apt install -y python3-certbot-dns-cloudflare
# 创建Cloudflare API凭据文件
sudo mkdir -p /etc/letsencrypt
sudo nano /etc/letsencrypt/cloudflare.ini
写入内容:
dns_cloudflare_api_token = your_cloudflare_api_token
# 设置安全权限
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
# 获取通配符证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.yourdomain.com" -d yourdomain.com
五、Nginx SSL完整配置
5.1 Certbot自动生成的配置
运行certbot --nginx后,Certbot会自动修改Nginx配置,添加SSL相关指令。
5.2 推荐的手动优化配置
# HTTP强制跳转HTTPS
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
# Let's Encrypt验证仍需HTTP
location /.well-known/acme-challenge/ {
root /var/www/myapp/public;
}
# 其他请求跳转HTTPS
location / {
return 301 https://yourdomain.com$request_uri;
}
}
# HTTPS主配置
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
root /var/www/myapp/public;
index index.php;
charset utf-8;
# SSL证书路径
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL协议和加密套件(Mozilla推荐)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# SSL会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全响应头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
# Laravel重写规则
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP-FPM处理
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param HTTPS on;
include fastcgi_params;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
# 禁止访问隐藏文件
location ~ /\.(?!well-known) {
deny all;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
}
5.3 应用配置
sudo nginx -t
sudo systemctl reload nginx
六、Laravel应用层SSL配置
6.1 强制HTTPS(AppServiceProvider)
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\URL;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// 生产环境强制HTTPS
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
}
6.2 信任代理(TrustProxies)
// app/Http/Middleware/TrustProxies.php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
protected $proxies = '*'; // 信任所有代理(负载均衡器)
protected function headers(): array
{
return [
Request::HEADER_X_FORWARDED_FOR,
Request::HEADER_X_FORWARDED_HOST,
Request::HEADER_X_FORWARDED_PORT,
Request::HEADER_X_FORWARDED_PROTO,
Request::HEADER_X_FORWARDED_AWS_ELB,
];
}
}
6.3 配置.env
APP_URL=https://yourdomain.com
APP_ENV=production
# 确保使用安全Cookie
SESSION_SECURE_COOKIE=true
SESSION_DOMAIN=yourdomain.com
6.4 清除并重建缓存
cd /var/www/myapp
php artisan config:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
七、自动续期配置
7.1 Certbot自动续期
# 检查自动续期定时器
sudo systemctl status certbot.timer
# 手动测试续期
sudo certbot renew --dry-run
7.2 续期后自动重载Nginx
# 创建续期后钩子
sudo nano /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
#!/bin/bash
# 续期成功后重载Nginx
systemctl reload nginx
# 可选:发送通知
curl -s "https://your-monitoring-endpoint/ssl-renewed?domain=yourdomain.com" > /dev/null
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
7.3 监控证书过期时间
# 查看证书过期时间
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -enddate
# 添加到监控脚本
#!/bin/bash
EXPIRY=$(echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt 14 ]; then
echo "WARNING: SSL certificate expires in $DAYS_LEFT days!"
# 发送告警通知
fi
八、常见问题排查
8.1 证书验证失败
# 检查证书文件是否存在
ls -la /etc/letsencrypt/live/yourdomain.com/
# 检查证书域名
openssl x509 -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem -noout -text | grep DNS
# 重新获取证书
sudo certbot --nginx -d yourdomain.com --force-renewal
8.2 混合内容警告
浏览器显示”不安全”通常是因为页面中存在HTTP资源引用:
# 检查Laravel中是否有硬编码HTTP
grep -r "http://" /var/www/myapp/resources/views/
# 解决方案:
# 1. 使用URL::asset()而非硬编码
# 2. 在.env中设置APP_URL=https://yourdomain.com
# 3. 使用URL::forceScheme('https')
8.3 重定向循环
# 检查Nginx是否同时设置了HTTP跳转和Laravel强制HTTPS
# 如果Nginx已经做了301跳转,Laravel不需要再forceScheme
# 排查方法:查看响应头
curl -I http://yourdomain.com
curl -I https://yourdomain.com
# 确保只在一处做跳转,不要Nginx和Laravel都跳转
8.4 OCSP Stapling失败
# 测试OCSP Stapling
echo QUIT | openssl s_client -connect yourdomain.com:443 -status 2>/dev/null | grep -A2 "OCSP response"
# 如果失败,检查DNS resolver配置
# 临时禁用OCSP Stapling
# ssl_stapling off;
九、SSL安全测试
使用在线工具验证SSL配置安全性:
| 工具 | 地址 | 检测内容 |
|---|---|---|
| SSL Labs | ssllabs.com/ssltest | 全面SSL安全评估 |
| Security Headers | securityheaders.com | HTTP安全头检测 |
| HSTS Preload | hstspreload.org | HSTS预加载状态 |
目标评分:SSL Labs A+、Security Headers A及以上
十、总结
| 步骤 | 命令/操作 | 说明 |
|---|---|---|
| 安装Certbot | apt install certbot python3-certbot-nginx |
SSL证书工具 |
| 获取证书 | certbot --nginx -d domain.com |
自动配置Nginx |
| 优化SSL配置 | 手动编辑Nginx配置 | TLS 1.2+1.3、HSTS、OCSP |
| Laravel配置 | forceScheme + TrustProxies | 应用层HTTPS |
| 自动续期 | certbot timer | 自动续期+重载 |
| 安全测试 | SSL Labs | 验证配置安全 |
关键提醒:
1. 证书有效期仅90天,必须配置自动续期
2. 始终在Nginx层做HTTPS跳转,避免与Laravel冲突
3. 使用Mozilla推荐的SSL配置,不要自行精简加密套件
4. 定期检查SSL安全评分,及时更新配置
注:本文基于Laravel 11.x、Nginx 1.24、Certbot 3.x、Debian 12编写。