跳转到内容

Nginx 应用部署实战

本文提供一份完整的 Nginx 应用部署手册,涵盖从安装 Nginx 到配置反向代理、SSL 证书、SELinux 策略和日志轮转的全过程。适用于将 Nginx 作为前端代理转发到 Node.js、Python (Gunicorn/uWSGI) 或 PHP-FPM 后端的典型场景。

  • 服务器已完成 新机上线基线清单
  • 已有域名并解析到服务器 IP
  • 后端应用已部署并可在本地端口(如 127.0.0.1:3000)上运行
Terminal window
dnf install -y nginx

使用 Nginx 官方仓库(推荐获取最新稳定版)

Section titled “使用 Nginx 官方仓库(推荐获取最新稳定版)”
Terminal window
cat > /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
dnf install -y nginx

启动并启用服务:

Terminal window
systemctl enable --now nginx
# 验证
systemctl status nginx
curl -I http://localhost

移除默认配置并创建应用专属配置:

Terminal window
# 备份默认配置
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak

创建应用配置文件 /etc/nginx/conf.d/myapp.conf

# 上游后端定义
upstream myapp_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
# 访问日志与错误日志
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
# 安全头
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;
# 静态文件(如果有)
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 反向代理到后端
location / {
proxy_pass http://myapp_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查端点
location /health {
proxy_pass http://myapp_backend/health;
access_log off;
}
}

PHP-FPM 后端:

upstream php_backend {
server unix:/run/php-fpm/www.sock;
}
location ~ \.php$ {
fastcgi_pass php_backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

Python (Gunicorn) 后端:

upstream gunicorn_backend {
server unix:/run/gunicorn/myapp.sock;
}
location / {
proxy_pass http://gunicorn_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

测试并重载配置:

Terminal window
nginx -t
systemctl reload nginx

第 3 步:SSL 证书(Let’s Encrypt + Certbot)

Section titled “第 3 步:SSL 证书(Let’s Encrypt + Certbot)”
Terminal window
dnf install -y epel-release
dnf install -y certbot python3-certbot-nginx
Terminal window
certbot --nginx -d example.com -d www.example.com

Certbot 会自动修改 Nginx 配置以启用 SSL。验证生成的配置:

Terminal window
nginx -t

Certbot 安装后会自动创建 systemd timer:

Terminal window
# 查看续期定时器
systemctl list-timers | grep certbot
# 手动测试续期(dry-run)
certbot renew --dry-run

/etc/nginx/conf.d/ssl-params.conf 中添加全局 SSL 优化:

# SSL 协议和密码套件
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';
ssl_prefer_server_ciphers on;
# 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 1.1.1.1 valid=300s;
resolver_timeout 5s;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

SELinux 默认不允许 Nginx 向网络后端发起连接。必须配置相应布尔值:

Terminal window
# 允许 Nginx 连接到网络后端
setsebool -P httpd_can_network_connect 1
# 如果 Nginx 需要访问用户主目录下的内容
setsebool -P httpd_enable_homedirs 1
# 如果 Nginx 需要连接数据库
setsebool -P httpd_can_network_connect_db 1

如果静态文件位于非标准路径,需要设置 SELinux 上下文:

Terminal window
# 设置 Web 内容标签
semanage fcontext -a -t httpd_sys_content_t "/var/www/myapp(/.*)?"
restorecon -Rv /var/www/myapp

如果 Nginx 监听非标准端口:

Terminal window
# 添加端口标签(例如端口 8080)
semanage port -a -t http_port_t -p tcp 8080

排查 SELinux 拒绝日志:

Terminal window
# 查看拒绝记录
ausearch -m AVC -ts recent
# 自动生成策略建议
audit2why < /var/log/audit/audit.log
Terminal window
# 开放 HTTP 和 HTTPS
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
# 重新加载
firewall-cmd --reload
# 验证
firewall-cmd --list-services

第 6 步:后端应用的 Systemd 服务

Section titled “第 6 步:后端应用的 Systemd 服务”

为后端应用创建 systemd 服务单元,以 Node.js 应用为例:

Terminal window
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Application
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
# 环境变量
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=-/opt/myapp/.env
# 安全加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data /var/log/myapp
PrivateTmp=true
# 资源限制
LimitNOFILE=65536
MemoryMax=512M
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
EOF

Gunicorn 示例:

Terminal window
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=Gunicorn Application Server
After=network-online.target
[Service]
Type=notify
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/gunicorn/myapp.sock \
--access-logfile /var/log/myapp/access.log \
--error-logfile /var/log/myapp/error.log \
wsgi:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RuntimeDirectory=gunicorn
[Install]
WantedBy=multi-user.target
EOF

启动服务:

Terminal window
# 创建应用用户
useradd -r -s /sbin/nologin appuser
# 设置目录权限
chown -R appuser:appuser /opt/myapp
systemctl daemon-reload
systemctl enable --now myapp
# 验证
systemctl status myapp
curl http://127.0.0.1:3000/health

确保后端应用实现了 /health 端点,返回当前服务状态。最小示例(Node.js):

app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
uptime: process.uptime(),
timestamp: Date.now()
});
});

创建一个简单的健康检查脚本:

cat > /usr/local/bin/health-check.sh << 'SCRIPT'
#!/bin/bash
URL="http://127.0.0.1/health"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL" --max-time 5)
if [ "$HTTP_CODE" != "200" ]; then
echo "[$(date)] Health check failed: HTTP $HTTP_CODE" >> /var/log/health-check.log
systemctl restart myapp
systemctl reload nginx
fi
SCRIPT
chmod +x /usr/local/bin/health-check.sh

添加定时检查:

Terminal window
cat > /etc/cron.d/health-check << 'EOF'
*/5 * * * * root /usr/local/bin/health-check.sh
EOF

部署完成后,进行完整的端到端测试:

Terminal window
# HTTP 到 HTTPS 重定向
curl -I http://example.com
# HTTPS 正常响应
curl -I https://example.com
# 后端健康检查
curl https://example.com/health
# SSL 证书信息
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

Nginx 的默认日志轮转通常已配置。为应用自定义日志添加规则:

Terminal window
cat > /etc/logrotate.d/myapp << 'EOF'
/var/log/myapp/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 appuser appuser
sharedscripts
postrotate
/bin/systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
/var/log/nginx/myapp_*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
/bin/kill -USR1 $(cat /run/nginx.pid 2>/dev/null) 2>/dev/null || true
endscript
}
EOF

测试配置:

Terminal window
logrotate -d /etc/logrotate.d/myapp
序号检查项验证命令预期结果
1Nginx 运行systemctl status nginxactive
2配置语法nginx -tsyntax is ok
3SSL 证书certbot certificates有效且未过期
4SELinux 布尔值getsebool httpd_can_network_connecton
5防火墙firewall-cmd --list-serviceshttp https
6后端服务systemctl status myappactive
7健康检查curl http://127.0.0.1/health200 OK
8HTTPS 访问curl -I https://example.com200 OK
9日志轮转logrotate -d /etc/logrotate.d/myapp无错误
问题排查步骤
502 Bad Gateway检查后端服务是否运行;检查 SELinux httpd_can_network_connect;查看 /var/log/nginx/myapp_error.log
403 Forbidden检查文件权限;检查 SELinux 上下文 ls -Z;运行 restorecon
SSL 证书过期运行 certbot renew;检查 systemd timer systemctl status certbot-renew.timer
连接被拒检查 firewall-cmd --list-all;确认 Nginx 监听正确端口 ss -tlnp