LNMP 环境搭建指南(Ubuntu)

> nginx 1.28 + PHP 8.5 + MySQL 8.4 完整安装配置,含 PHP-FPM 调优、虚拟主机、SSL、502/504 故障排查。


1. 系统准备


sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 ca-certificates lsb-release \
  software-properties-common unzip zip


2. nginx 1.28 安装

2.1 添加官方仓库


# 添加 nginx 官方 APT 仓库
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor \
  -o /usr/share/keyrings/nginx-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update
sudo apt install -y nginx=1.28.*

2.2 验证安装


nginx -v
# nginx version: nginx/1.28.0

sudo systemctl status nginx
sudo systemctl enable nginx

2.3 nginx 目录结构


/etc/nginx/
├── nginx.conf              # 主配置文件
├── conf.d/                 # HTTP server 配置片段
├── sites-available/        # 可用站点配置
├── sites-enabled/          # 已启用站点(符号链接到 sites-available)
├── snippets/               # 可复用的配置片段
│   ├── ssl-params.conf
│   └── proxy-params.conf
├── fastcgi_params          # FastCGI 标准参数
└── mime.types              # MIME 类型映射

/var/log/nginx/
├── access.log # 访问日志
└── error.log # 错误日志

/var/www/ # 网站根目录


3. PHP 8.5 安装

3.1 添加 PHP 仓库


# 添加 ondrej/php PPA
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

3.2 安装 PHP 8.5 + FPM


sudo apt install -y \
  php8.5-fpm \
  php8.5-cli \
  php8.5-common \
  php8.5-mysql \
  php8.5-mbstring \
  php8.5-xml \
  php8.5-curl \
  php8.5-gd \
  php8.5-zip \
  php8.5-intl \
  php8.5-bcmath \
  php8.5-imagick \
  php8.5-redis \
  php8.5-opcache

3.3 验证


php -v
# PHP 8.5.x (cli)

sudo systemctl status php8.5-fpm
sudo systemctl enable php8.5-fpm

3.4 常用 PHP 配置


# 配置文件位置
# PHP CLI:   /etc/php/8.5/cli/php.ini
# PHP-FPM:   /etc/php/8.5/fpm/php.ini
# Pool 配置: /etc/php/8.5/fpm/pool.d/www.conf

关键 php.ini 调整:


# /etc/php/8.5/fpm/php.ini

# 上传限制
upload_max_filesize = 100M
post_max_size = 120M
max_execution_time = 300
max_input_time = 300

# 内存限制
memory_limit = 256M

# 错误报告(生产环境)
display_errors = Off
log_errors = On
error_log = /var/log/php8.5-fpm.log

# 时区
date.timezone = Asia/Shanghai

# OPcache
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 2


4. PHP-FPM www.conf 调优

4.1 进程管理模式

编辑 /etc/php/8.5/fpm/pool.d/www.conf


; 进程管理模式:dynamic / static / ondemand
pm = dynamic

; dynamic 模式下的关键参数
; 最大子进程数 = (总内存 - 预留内存) / 每个进程内存
; 假设总内存 4GB,预留 1GB 给系统/nginx/mysql,每进程 ~50MB
; 则 max_children ≈ (3072) / 50 ≈ 60

pm.max_children = 60 ; 最大子进程数
pm.start_servers = 10 ; 启动时创建的子进程数
pm.min_spare_servers = 5 ; 最小空闲进程数
pm.max_spare_servers = 20 ; 最大空闲进程数
pm.max_requests = 500 ; 每个进程处理 500 个请求后重启(防内存泄漏)

4.2 模式选择指南

模式 适用场景 优点 缺点
dynamic 一般 Web 应用 自动扩缩、内存效率好 突发流量时响应稍慢
static 高并发 API 无 fork 开销、响应快 始终占用最大内存
ondemand 低频访问站点 空闲时不占内存 请求到来时有 fork 延迟

4.3 调优计算器


#!/bin/bash
# 计算建议的 max_children 值
# 保存为 /home/user/tools/php-fpm-tune.sh

TOTAL_MEM_MB=$(free -m | awk '/^Mem:/{print $2}')
RESERVED_MB=1024 # 给系统 + nginx + MySQL 预留
AVAILABLE_MB=$((TOTAL_MEM_MB - RESERVED_MB))

# 获取单个 PHP-FPM 进程的平均内存(MB)
PHP_PROC_MEM=$(ps -eo rss,cmd | grep "php-fpm: pool www" | \
awk '{sum+=$1} END {if (NR>0) printf "%.0f", sum/NR/1024; else print "50"}')

MAX_CHILDREN=$((AVAILABLE_MB / PHP_PROC_MEM))

echo "Total Memory: ${TOTAL_MEM_MB} MB"
echo "Available: ${AVAILABLE_MB} MB"
echo "PHP Process Avg: ${PHP_PROC_MEM} MB"
echo "Suggested max_children: ${MAX_CHILDREN}"
echo ""
echo "Recommended www.conf settings:"
echo " pm.max_children = ${MAX_CHILDREN}"
echo " pm.start_servers = $((MAX_CHILDREN / 4))"
echo " pm.min_spare_servers = $((MAX_CHILDREN / 8))"
echo " pm.max_spare_servers = $((MAX_CHILDREN / 3))"

4.4 Socket vs TCP


; Unix Socket(推荐,同机部署,性能更好)
listen = /run/php/php8.5-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; TCP Socket(跨服务器部署时需要)
; listen = 127.0.0.1:9000
; listen.allowed_clients = 127.0.0.1

4.5 应用配置并重启


# 修改配置后测试
sudo php-fpm8.5 -t

# 重启
sudo systemctl restart php8.5-fpm

# 查看进程数
ps aux | grep php-fpm | wc -l


5. MySQL 8.4 安装

5.1 安装


# 添加 MySQL APT 仓库
wget https://dev.mysql.com/get/mysql-apt-config_0.8.32-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.32-1_all.deb
# 选择 MySQL 8.4 LTS → OK

sudo apt update
sudo apt install -y mysql-server mysql-client

5.2 初始化安全配置


sudo mysql_secure_installation
# 按提示设置:
# - VALIDATE PASSWORD: 根据需要选择
# - root 密码:设置强密码
# - 移除匿名用户:Y
# - 禁止 root 远程登录:Y(建议)
# - 删除 test 数据库:Y
# - 重新加载权限表:Y

5.3 验证


sudo systemctl status mysql
sudo mysql -u root -p -e "SELECT VERSION();"

5.4 创建应用数据库和用户


-- 进入 MySQL
sudo mysql -u root -p

-- 创建数据库
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
CREATE USER 'myapp_user'@'%' IDENTIFIED BY 'StrongPassword123!';

-- 授予权限(本地权限)
GRANT ALL PRIVILEGES ON myapp.* TO 'myapp_user'@'localhost';

-- 授予远程权限(如需要)
GRANT ALL PRIVILEGES ON myapp.* TO 'myapp_user'@'%';

-- 刷新权限
FLUSH PRIVILEGES;

-- 验证
SHOW GRANTS FOR 'myapp_user'@'localhost';
EXIT;

5.5 MySQL 远程访问配置


# 编辑 MySQL 配置文件
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf


# 将 bind-address 从 127.0.0.1 改为 0.0.0.0(或注释掉)
bind-address = 0.0.0.0
mysqlx-bind-address = 0.0.0.0


# 重启 MySQL
sudo systemctl restart mysql

# 如果开启了防火墙
sudo ufw allow 3306/tcp

5.6 常用管理命令


-- 查看连接数
SHOW STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';

-- 查看当前连接
SHOW PROCESSLIST;

-- 杀死长时间运行的查询
KILL <process_id>;

-- 查看数据库大小
SELECT table_schema AS "Database",
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS "Size (MB)"
FROM information_schema.tables
GROUP BY table_schema;

-- 创建备份用户(只读)
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'BackupPass123!';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;

5.7 自动备份脚本


#!/bin/bash
# 保存为 /home/user/tools/mysql-backup.sh
BACKUP_DIR="/home/user/backups/mysql"
MYSQL_USER="backup_user"
MYSQL_PASS="BackupPass123!"
DATABASES="myapp"
RETENTION_DAYS=7

mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)

for DB in $DATABASES; do
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASS" \
--single-transaction --routines --triggers --events \
"$DB" | gzip > "${BACKUP_DIR}/${DB}_${DATE}.sql.gz"
done

# 删除 7 天前的备份
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: ${BACKUP_DIR}/"
ls -lh "${BACKUP_DIR}/" | tail -5


# 添加到 crontab(每天凌晨 2 点)
# crontab -e
# 0 2 * * * /bin/bash /home/user/tools/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1


6. nginx Server Block(虚拟主机)

6.1 目录结构建议


/var/www/
├── myapp.com/
│   ├── public/              # Web 根目录(index.php 等)
│   ├── logs/                # 站点日志
│   └── private/             # 不应直接访问的文件
├── api.myapp.com/
│   └── public/
└── ...

6.2 基础静态站点配置


# /etc/nginx/sites-available/myapp.com
server {
    listen 80;
    listen [::]:80;

server_name myapp.com www.myapp.com;

root /var/www/myapp.com/public;
index index.html index.htm;

access_log /var/log/nginx/myapp-access.log;
error_log /var/log/nginx/myapp-error.log warn;

location / {
try_files $uri $uri/ =404;
}

# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
}

6.3 PHP 站点完整配置


# /etc/nginx/sites-available/myapp.com
server {
    listen 80;
    listen [::]:80;
    server_name myapp.com www.myapp.com;

root /var/www/myapp.com/public;
index index.php index.html index.htm;

access_log /var/log/nginx/myapp-access.log;
error_log /var/log/nginx/myapp-error.log warn;

# 主 location
location / {
try_files $uri $uri/ /index.php?$query_string;
}

# PHP 处理
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;

# Unix Socket 方式
fastcgi_pass unix:/run/php/php8.5-fpm.sock;

# TCP 方式
# fastcgi_pass 127.0.0.1:9000;

fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}

# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}

# 禁止访问隐藏文件和敏感目录
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

# 禁止直接访问 private 目录
location ~ ^/private/ {
deny all;
return 404;
}

# =404 兜底
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
}
}

6.4 反向代理配置(API 后端)


# /etc/nginx/sites-available/api.myapp.com
upstream api_backend {
    # 负载均衡
    server 127.0.0.1:8080 weight=5;
    server 127.0.0.1:8081 weight=3;
    # server 10.0.0.2:8080 backup;   # 备份服务器

keepalive 32;
}

server {
listen 80;
server_name api.myapp.com;

access_log /var/log/nginx/api-access.log;
error_log /var/log/nginx/api-error.log;

location / {
proxy_pass http://api_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;

# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
}

6.5 启用站点


# 创建符号链接
sudo ln -s /etc/nginx/sites-available/myapp.com /etc/nginx/sites-enabled/

# 删除默认站点(如需要)
sudo rm /etc/nginx/sites-enabled/default

# 测试配置
sudo nginx -t

# 重载
sudo nginx -s reload


7. SSL/TLS + Let's Encrypt

7.1 安装 Certbot


sudo apt install -y certbot python3-certbot-nginx

7.2 签发证书


# 自动配置 nginx(推荐)
sudo certbot --nginx -d myapp.com -d www.myapp.com

# 或仅获取证书(手动配置 nginx)
sudo certbot certonly --webroot \
-w /var/www/myapp.com/public \
-d myapp.com -d www.myapp.com

7.3 SSL 配置片段

创建 /etc/nginx/snippets/ssl-params.conf


# SSL 安全参数
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
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;

# DH 参数
ssl_dhparam /etc/nginx/dhparam.pem;

# 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# HSTS(谨慎开启,一旦开启很难回退)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

7.4 生成 DH 参数


sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# 注意:2048 位约需 1-2 分钟,4096 位约需 10-30 分钟

7.5 完整 HTTPS 站点配置


# /etc/nginx/sites-available/myapp.com
server {
    # HTTP → HTTPS 重定向
    listen 80;
    listen [::]:80;
    server_name myapp.com www.myapp.com;
    return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name myapp.com www.myapp.com;

# SSL 证书
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;

include snippets/ssl-params.conf;

root /var/www/myapp.com/public;
index index.php index.html index.htm;

access_log /var/log/nginx/myapp-ssl-access.log;
error_log /var/log/nginx/myapp-ssl-error.log warn;

location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}

location ~ /\. {
deny all;
}
}

7.6 自动续期

Certbot 默认会自动配置 systemd timer。验证:


sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

手动测试续期(干运行):


sudo certbot renew --dry-run

如果 certbot.timer 未自动配置,手动添加:


# 创建 systemd service
sudo tee /etc/systemd/system/certbot-renewal.service << 'EOF'
[Unit]
Description=Certbot SSL Renewal
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --post-hook "nginx -s reload"
EOF

# 创建 timer(每天 03:00 + 随机延迟)
sudo tee /etc/systemd/system/certbot-renewal.timer << 'EOF'
[Unit]
Description=Daily Certbot SSL Renewal

[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renewal.timer


8. 502/504 错误诊断

8.1 502 Bad Gateway

含义:nginx 无法从上游服务器(PHP-FPM)获取有效响应。 诊断流程

# 1. 检查 PHP-FPM 是否运行
sudo systemctl status php8.5-fpm
ps aux | grep php-fpm

# 2. 检查 Socket 文件
ls -la /run/php/php8.5-fpm.sock
# 检查权限和所有者(应为 www-data:www-data)

# 3. 检查 nginx FastCGI 配置
# 确认 fastcgi_pass 的 socket 路径与 PHP-FPM 一致
grep -r "fastcgi_pass" /etc/nginx/sites-enabled/

# 4. 查看错误日志
sudo tail -50 /var/log/nginx/error.log
sudo tail -50 /var/log/php8.5-fpm.log

# 5. 检查 PHP-FPM 是否耗尽子进程
sudo tail -20 /var/log/php8.5-fpm.log | grep "max_children"

常见原因及解决方案
原因 检测方法 解决方案
PHP-FPM 未运行 systemctl status sudo systemctl restart php8.5-fpm
Socket 路径不匹配 grep fastcgi_pass vs ls /run/php/ 统一 socket 路径
Socket 权限错误 ls -la /run/php/ > 权限不是 0660、owner 不是 www-data 修改 www.conf 的 listen 权限
进程耗尽 日志有 "max_children reached" 增大 pm.max_children
PHP 脚本超时 脚本执行时间 > max_execution_time 优化代码或增大超时
内存耗尽 PHP 进程 OOM,dmesg 查看 减小 max_children 或增加内存
SELinux/AppArmor sudo aa-status \| grep php 调整 AppArmor 规则

8.2 504 Gateway Timeout

含义:nginx 等待上游服务器响应超时。 诊断流程

# 1. 检查 nginx 超时设置
grep -E "(fastcgi_read_timeout|proxy_read_timeout)" /etc/nginx/sites-enabled/*

# 2. 检查 PHP 执行时间
grep "max_execution_time" /etc/php/8.5/fpm/php.ini

# 3. 检查慢查询(MySQL)
sudo tail -50 /var/log/mysql/slow.log

# 4. 确认是哪个请求超时
sudo tail -100 /var/log/nginx/access.log | awk '$9==504'

解决方案

# 在 nginx location 中增加超时
location ~ \.php$ {
    fastcgi_read_timeout 300s;    # 从默认 60s 增加
    fastcgi_send_timeout 300s;
    # ... 其他配置
}

# 对于 proxy_pass
location /api/ {
proxy_read_timeout 300s;
proxy_connect_timeout 30s;
# ... 其他配置
}


; php.ini 增加执行时间(如确有必要)
max_execution_time = 300
max_input_time = 300

8.3 诊断脚本


#!/bin/bash
# 保存为 /home/user/tools/lnmp-diag.sh
echo "=== LNMP Diagnostics ==="
echo ""

# nginx
echo "[nginx]"
sudo systemctl is-active nginx || echo " WARNING: nginx not running"
sudo nginx -t 2>&1 | grep -E "ok|error"
echo " Active connections: $(curl -s http://localhost/nginx_status 2>/dev/null | grep 'Active' || echo 'stub_status not enabled')"
echo ""

# PHP-FPM
echo "[PHP-FPM]"
sudo systemctl is-active php8.5-fpm || echo " WARNING: PHP-FPM not running"
echo " Socket: $(ls -la /run/php/php8.5-fpm.sock 2>/dev/null || echo 'NOT FOUND')"
FPM_PROCS=$(ps aux | grep "php-fpm: pool" | wc -l)
echo " Worker processes: $FPM_PROCS"
echo " Last errors:"
sudo tail -3 /var/log/php8.5-fpm.log 2>/dev/null
echo ""

# MySQL
echo "[MySQL]"
sudo systemctl is-active mysql || echo " WARNING: MySQL not running"
echo " Connections: $(sudo mysqladmin status 2>/dev/null | grep -oP 'Threads: \d+' || echo 'N/A')"
echo ""

# 最近的 HTTP 错误
echo "[Recent 502/504 errors]"
sudo tail -200 /var/log/nginx/access.log 2>/dev/null | awk '$9~/502|504/' | tail -5
echo ""
echo "=== Done ==="


9. 性能优化补充

9.1 nginx 全局优化


# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;               # 自动匹配 CPU 核心数
worker_rlimit_nofile 65535;

events {
worker_connections 4096;
use epoll;
multi_accept on;
}

http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;

keepalive_timeout 65;
keepalive_requests 100;

# Gzip 压缩
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml
application/xml+rss text/javascript image/svg+xml;
gzip_proxied any;
gzip_disable "msie6";

# 客户端上传大小
client_max_body_size 100M;
client_body_buffer_size 128k;

# 在 conf.d/ 和 sites-enabled/ 中加载配置
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

9.2 MySQL 基础调优


# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]

# InnoDB 缓冲池(设为总内存的 50-70%)
innodb_buffer_pool_size = 2G
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# 连接数
max_connections = 200

# 查询缓存(MySQL 8.0+ 已移除)
# 改用应用程序级缓存(Redis/Memcached)

# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

# 字符集
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci


10. 一键部署脚本


#!/bin/bash
# 保存为 /home/user/tools/deploy-php-site.sh
# 用法: bash deploy-php-site.sh myapp.com

set -e

DOMAIN=${1:?Usage: $0 domain.com}
WEBROOT="/var/www/${DOMAIN}/public"
NGINX_CONF="/etc/nginx/sites-available/${DOMAIN}"

echo "=== Deploying ${DOMAIN} ==="

# 1. 创建目录
sudo mkdir -p "${WEBROOT}"
sudo mkdir -p "/var/www/${DOMAIN}/logs"
sudo chown -R www-data:www-data "/var/www/${DOMAIN}"

# 2. 创建默认页面
sudo tee "${WEBROOT}/index.php" << 'PHPEOF'
<?php phpinfo(); ?>
PHPEOF

# 3. 创建 nginx 配置
sudo tee "$NGINX_CONF" << NGXEOF
server {
listen 80;
server_name ${DOMAIN} www.${DOMAIN};
root ${WEBROOT};
index index.php index.html;

access_log /var/log/nginx/${DOMAIN}-access.log;
error_log /var/log/nginx/${DOMAIN}-error.log;

location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}

location ~ \.php\$ {
try_files \$uri =404;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
}

location ~ /\. { deny all; }
}
NGXEOF

# 4. 启用站点
sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/

# 5. 测试并重载
sudo nginx -t && sudo nginx -s reload

# 6. 可选:申请 SSL
read -p "Apply Let's Encrypt SSL? [y/N] " apply_ssl
if [ "$apply_ssl" = "y" ] || [ "$apply_ssl" = "Y" ]; then
sudo certbot --nginx -d "${DOMAIN}" -d "www.${DOMAIN}"
fi

echo "=== Site ${DOMAIN} deployed ==="
echo "URL: http://${DOMAIN}/"


11. 参考链接