前言:一台服务器,为什么要托管多个网站
个人站长手里通常不止一个站点:一个技术博客、一个资源下载站、可能还帮朋友托管一个企业官网。如果每个站都单独买一台服务器,成本高得离谱;把多个网站放在同一台 Nginx 上,才是性价比最高的方案。Nginx 通过"虚拟主机"(server block)机制,可以在同一个 IP、同一个 80/443 端口上同时服务成百上千个域名,互不干扰。本文从目录规划讲起,带你完整搭建一套多站点 Nginx 环境,并避开新手最容易踩的坑。文章里的配置都经过实际验证,你可以直接复制修改使用,建议边读边在测试机上动手操作一遍,理解会更深刻。
一、先规划目录结构
多站点管理的第一原则是"一个域名一个目录,配置和网站文件分开"。推荐的结构如下:
/var/www/
├── example.com/ # 站点A,域名即目录名
│ └── public_html/ # 网站根目录
├── blog.example.com/ # 站点B
│ └── public_html/
/etc/nginx/
├── sites-available/ # 存放所有站点的配置(可禁用的"开关")
│ ├── example.com.conf
│ └── blog.example.com.conf
└── sites-enabled/ # 软链接指向启用的站点
├── example.com.conf -> ../sites-available/example.com.conf
└── blog.example.com.conf -> ../sites-available/blog.example.com.confDebian/Ubuntu 系的 Nginx 默认就是这种 sites-available / sites-enabled 结构:配置放在 available 里,用 ln -s 软链接到 enabled 就算启用,删掉软链接就是禁用,管理起来非常清晰。CentOS 系的 nginx.conf 默认没有这两个目录,需要自己创建,并在 nginx.conf 的 http 块里加上 include /etc/nginx/conf.d/*.conf; 或者对应路径。
二、写第一个 server block
Nginx 配置的核心就是 server 块。每个 server 块声明一组监听规则和对应的网站根目录,Nginx 根据请求的 Host 头(也就是用户访问的域名)来决定由哪个 server 块处理。一个托管 PHP 程序的站点配置长这样:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}几个关键点:server_name 决定这个站点响应哪个域名;root 指向站点根目录;PHP 请求通过 fastcgi 转发给 PHP-FPM 处理。第二个站点复制一份配置,改掉 server_name、root 和日志路径即可,其余完全一样。如果第二个站点是纯静态站,把 PHP 相关的 location 删掉就行。
三、启用、测试与重载
配置文件写好后,按下面的顺序操作,先测试再生效,避免语法错误把整个 Nginx 搞挂:
# 1. 创建软链接启用站点
ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
# 2. 语法检查,输出 syntax is ok 才算通过
nginx -t
# 3. 重新加载配置(平滑生效,不中断连接)
systemctl reload nginx # 或 nginx -s reloadnginx -t 会检查所有配置文件的语法,如果有问题会明确提示出错的文件和行号。reload 是平滑重载,正在处理的请求不会中断,这是 Nginx 多站点管理里最常用的操作,改完配置第一时间 reload 而不是 restart。
四、没有匹配域名的请求怎么办:默认 server
多站点配置里最容易被忽略的安全问题:如果有人直接用 IP 访问你的服务器,或者用别的域名解析到你的 IP 再访问,请求会落在哪个站点?Nginx 的规则是"第一个 server 块"或者"listen 指令带 default_server 标记"的块。如果你不处理,别人的域名解析到你的 IP 上就能看到你某个站点的内容,还可能被用来做恶意跳转。正确的做法是单独建一个默认站点,直接拒绝这些请求:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}把这段配置放在最前面(或者作为第一个加载的站点),任何不认识 Host 的请求都会被直接断开连接(444 是 Nginx 特有的"直接关闭连接"状态码)。注意:这个默认 server 块必须在其他站点之前被 include,所以文件名用 00-default.conf 之类的排序靠前的名字最稳妥。
五、日志分离:每个站点一份独立的访问日志
多站点如果不做日志分离,所有访问记录都堆在同一个 access.log 里,排查某个站点的问题时要在海量记录里大海捞针。正确的做法是在每个 server 块里单独指定日志路径:
server {
server_name example.com www.example.com;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
# 还可以按域名单独统计
access_log /var/log/nginx/example.com.access.log combined;
}日志分开之后,配合 logrotate 对每个站点的日志独立轮转(比如流量大的站点日志一天一切,小站点一周一切),再用 goaccess 之类的工具对单站日志做访问统计,每个站点的流量来源、热门页面、访问者地区都清清楚楚。对个人站长来说,日志就是最真实的运营数据,分离日志是投入产出比极高的一件事。另外建议在 logrotate 配置里给 Nginx 日志加上 copytruncate 或者 postrotate 里执行 reload,避免轮转时产生前面说的"幽灵文件"占用磁盘的情况。
六、HTTPS 多站点:SNI 自动匹配证书
现在全站 HTTPS 是标配。多站点环境下每个域名都要有自己的证书,Nginx 通过 SNI(Server Name Indication)机制,在 TLS 握手阶段就根据域名自动选择对应证书,完全不需要额外配置:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 其他配置与 80 端口相同...
}
# HTTP 跳转 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}用 certbot 申请证书时,certbot --nginx -d example.com -d www.example.com 会自动帮你改好 Nginx 配置。每个域名独立申请证书,互不影响,证书到期续期时也只影响对应站点。
七、按端口和 IP 区分站点的特殊用法
除了按域名区分,Nginx 还支持按端口或按 IP 区分站点。端口区分适合"内部工具站"场景,比如同一个 IP 上,80 端口跑正式网站,8080 端口跑一个只给自己用的管理面板,别人不知道端口号就无法访问。配置方法就是在 listen 指令里指定端口:
server {
listen 8080;
server_name _;
root /var/www/internal-tool;
# 可以加上内网 IP 限制
allow 192.168.1.0/24;
deny all;
}IP 区分则适用于服务器绑定了多个 IP 的情况(比如同时有 IPv4 和 IPv6,或者买了多个 IP 做不同业务),写法是 listen 1.2.3.4:80;。不过对个人站长来说,最常用的还是域名区分,端口和 IP 区分属于进阶用法,了解即可。另外提醒一点:用端口区分时,如果网站开启了防火墙,记得放行对应端口,否则外部访问会超时。
八、多站点下的 PHP-FPM 资源分配
多站点共用同一个 PHP-FPM 服务时,要注意资源分配问题。默认情况下所有站点共用一个 FPM 进程池,某个站点流量暴涨占满所有 worker 进程,其他站点就会跟着 502。解决办法是给重要站点单独建一个 FPM 池:在 /etc/php/8.2/fpm/pool.d/ 下新建一个配置文件(比如 blog.conf),里面设置独立的 listen 端口或 socket、独立的用户和进程数上限:
[blog]
user = www-blog
group = www-blog
listen = /run/php/php8.2-fpm-blog.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 5然后在 Nginx 里把该站点的 fastcgi_pass 指向新的 socket,两个站点的 PHP 处理就完全隔离了。代价是多占一些内存,但对有重要业务的站长来说非常值得。内存紧张的服务器可以适当调小 max_children,按每个 PHP-FPM 进程平均占用 30M 内存估算,1G 内存的机器留出系统占用后,max_children 设 20 左右比较稳妥。
九、多站点常见坑与排错
最后总结新手最容易踩的五个坑。第一个坑是 server_name 写错导致访问一个域名却打开了另一个站点的内容,检查时用 curl -H "Host: example.com" http://IP/ 直接指定 Host 测试;第二个坑是忘记创建软链接,配置写了但没启用,nginx -T 可以列出当前实际生效的全部 server 块,一眼看出哪个没加载;第三个坑是 PHP-FPM 的 socket 权限问题,跨站点时报 502 或者 File not found,检查 fastcgi_param SCRIPT_FILENAME 是否使用了 $document_root;第四个坑是根目录权限不对导致 403,站点目录属主和 PHP-FPM 运行用户要一致;第五个坑是站点上线后改配置习惯用 restart 而不是 reload,restart 会瞬间中断所有正在处理的请求,高峰期操作可能造成用户访问报错,而 reload 是平滑重载,不影响现有连接,养成"改配置先 nginx -t 再 reload"的习惯,多站点管理就基本不会出大问题了。随着站点数量增加,这套规范化的管理方式会越来越体现出价值,也为你以后用 Ansible 之类的工具批量管理服务器打下了好基础。