单服务器多网站部署:Nginx虚拟主机配置与实战指南

发布时间:2026/8/5 5:42:35
单服务器多网站部署:Nginx虚拟主机配置与实战指南 1. 项目概述单服务器多网站的Nginx部署策略在运维和开发工作中我们常常会遇到一个非常实际的需求手头只有一台服务器但需要承载多个独立的网站或Web应用。这些应用可能服务于不同的业务线拥有各自独立的域名。直接购买多台服务器显然成本高昂而在一台服务器上通过端口号如8080、8081来区分又显得不够专业用户体验也差。这时Nginx作为一款高性能的HTTP和反向代理服务器其基于域名的虚拟主机功能就成了解决这个问题的“瑞士军刀”。简单来说这个项目的核心就是利用Nginx的配置能力让一台服务器能够根据用户访问时使用的不同域名将请求准确地分发到服务器上对应的不同网站目录或后端服务端口。这不仅是资源利用最大化的体现更是现代Web服务架构中的基础操作。无论你是个人站长管理着几个博客和工具站还是中小企业的运维需要在一台云服务器上部署官网、后台管理系统和API服务亦或是开发者想在测试环境模拟多域名场景掌握这项技能都至关重要。我经历过从Apache的httpd.conf里手写VirtualHost到如今在Nginx的server块中游刃有余的整个过程。实测下来Nginx的配置语法更清晰性能也更优尤其是在处理高并发静态资源时。接下来我将带你从零开始彻底搞懂如何在单台服务器上用Nginx优雅地部署多个网站并绑定各自的域名。我们会涵盖从环境准备、配置解析、SSL证书部署到日常维护和深度优化的全流程并分享我踩过的那些坑和总结出的实战技巧。2. 核心原理与架构设计拆解2.1 Nginx如何处理多域名请求要理解如何配置首先要明白Nginx的工作机制。当你在浏览器输入www.site-a.com并按下回车时背后发生了几件关键事情DNS解析你的计算机会向DNS服务器查询www.site-a.com对应的IP地址。最终site-a.com和site-b.com的域名都可能指向你服务器的同一个公网IP。请求到达携带了Host请求头其值为www.site-a.com的网络数据包到达你的服务器80或443端口。Nginx的抉择监听80/443端口的Nginx进程接收到这个请求。它并不会关心IP是什么因为IP都一样。它的核心判断依据是HTTP请求头中的Host字段。配置匹配Nginx会遍历其配置文件中所有的server块寻找server_name指令与请求的Host头值相匹配的那个块。请求路由一旦找到匹配的server块Nginx就会按照该块内的指令如root,index,proxy_pass等来处理请求例如从/var/www/site-a目录下读取文件或者将请求转发给运行在3000端口的Node.js应用。这个过程的关键在于**server_name指令**。它定义了该server块负责响应的域名。一个最简单的多网站配置骨架如下# 第一个网站 server { listen 80; server_name www.site-a.com site-a.com; root /var/www/site-a; index index.html; ... } # 第二个网站 server { listen 80; server_name www.site-b.com site-b.com; root /var/www/site-b; index index.php index.html; ... }Nginx会按照配置文件中的顺序进行匹配因此通常会把最具体或最重要的域名配置放在前面并设置一个默认的server块server_name _;来处理未匹配的请求通常返回444错误或重定向。2.2 单服务器多网站部署的几种模式根据网站的技术栈部署模式主要分为三类静态资源模式网站由纯HTML、CSS、JavaScript和图片构成。这是最简单的情况Nginx直接充当Web服务器通过root指令指定文件根目录即可。性能最佳适合博客、官网、文档站。动态应用模式网站由PHP、PythonDjango/Flask、Node.js、JavaSpring Boot等后端语言驱动。Nginx此时扮演反向代理的角色。它接收用户请求然后根据配置将请求转发proxy_pass到运行在服务器另一个端口上的后端应用进程。Nginx负责处理静态文件、负载均衡和SSL终止后端应用专心处理业务逻辑。这是目前最主流的模式。混合模式一个网站内同时存在静态资源和动态接口。通常通过location指令进行精细化路由。例如将所有对/static/路径的请求直接由Nginx处理而对/api/的请求则代理到后端。选择哪种模式决定了你Nginx配置的核心部分。我的建议是即使当前是纯静态站也按反向代理的思路来规划目录和配置为未来可能的动态化改造留出空间。3. 环境准备与Nginx基础配置3.1 服务器与Nginx安装假设我们使用一台全新的CentOS 7或Ubuntu 20.04 LTS服务器。系统的选择影响不大主要是包管理命令不同。对于CentOS/RHEL系列# 添加EPEL仓库如果需要 sudo yum install epel-release # 安装Nginx sudo yum install nginx # 启动并设置开机自启 sudo systemctl start nginx sudo systemctl enable nginx对于Ubuntu/Debian系列# 更新软件包列表 sudo apt update # 安装Nginx sudo apt install nginx # 启动并设置开机自启 sudo systemctl start nginx sudo systemctl enable nginx安装完成后在浏览器输入服务器IP应该能看到Nginx的欢迎页面。这证明Nginx已成功安装并运行。注意很多教程会教你去官网下载源码编译安装以获得最新特性或自定义模块。但对于绝大多数生产环境我强烈建议使用系统包管理器安装。理由有三第一管理方便升级、卸载一键完成第二安全性有保障系统仓库会及时提供安全更新第三符合系统规范配置文件、日志文件都在标准位置如/etc/nginx,/var/log/nginx减少运维认知负担。除非你有非常特殊的模块需求否则别折腾源码编译。3.2 理解Nginx配置文件结构这是很多新手容易迷糊的地方。Nginx的主配置文件通常是/etc/nginx/nginx.conf。但最佳实践是不要把所有配置都堆在这个文件里。标准的、良好的配置文件结构是这样的/etc/nginx/ ├── nginx.conf # 主配置文件包含全局设置worker进程数、日志格式等 ├── conf.d/ # **推荐** 存放自定义网站配置的目录 │ ├── site-a.conf # 网站A的配置 │ └── site-b.conf # 网站B的配置 ├── sites-available/ # 可用站点配置Ubuntu风格通过软链管理 ├── sites-enabled/ # 已启用站点配置指向sites-available下的文件 └── snippets/ # 可复用的配置片段如SSL参数、安全头nginx.conf 它会在http块内通过include指令引入其他目录的配置例如include /etc/nginx/conf.d/*.conf;。这样每个网站的配置都可以独立成一个文件放在conf.d目录下清晰且易于管理。conf.dvssites-available/enabled 前者是更通用的方式后者是Debian/Ubuntu系统包提供的管理习惯原理一样。我个人更倾向于使用conf.d因为它更直观跨发行版一致性更好。在开始配置前先备份原始配置是个好习惯sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup。4. 多网站配置实战从静态站到动态应用4.1 场景一部署两个静态网站假设我们需要部署个人博客域名blog.yourdomain.com文件位于/var/www/blog项目主页域名project.yourdomain.com文件位于/var/www/project第一步准备网站目录和文件# 创建网站根目录 sudo mkdir -p /var/www/blog sudo mkdir -p /var/www/project # 创建示例首页文件并设置正确的权限Nginx进程用户通常为nginx或www-data sudo bash -c echo h1Welcome to My Blog/h1 /var/www/blog/index.html sudo bash -c echo h1Project Homepage/h1 /var/www/project/index.html # 将目录所有权赋予Nginx运行用户根据你的系统调整用户组 sudo chown -R nginx:nginx /var/www/blog # CentOS # 或 sudo chown -R www-data:www-data /var/www/blog # Ubuntu sudo chmod -R 755 /var/www/blog # 对project目录执行相同操作第二步为每个网站创建独立的配置文件在/etc/nginx/conf.d/目录下创建两个文件blog.conf:server { # 监听80端口HTTP listen 80; # 监听IPv6地址的80端口如果服务器支持IPv6 listen [::]:80; # 指定该配置块负责的域名 server_name blog.yourdomain.com; # 网站文件的根目录 root /var/www/blog; # 默认索引文件 index index.html index.htm; # 日志文件便于排查问题 access_log /var/log/nginx/blog_access.log; error_log /var/log/nginx/blog_error.log; # 核心location块处理所有请求 location / { # 尝试以URI作为文件路径查找找不到则尝试作为目录查找索引文件最后返回404 try_files $uri $uri/ 404; } # 可以添加针对静态资源的缓存优化 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 30d; add_header Cache-Control public, immutable; } }project.conf:server { listen 80; listen [::]:80; server_name project.yourdomain.com; root /var/www/project; index index.html index.htm; access_log /var/log/nginx/project_access.log; error_log /var/log/nginx/project_error.log; location / { try_files $uri $uri/ 404; } }第三步测试配置并重载NginxNginx配置的语法非常严格一个多余的分号或拼写错误都会导致服务失败。# 测试配置文件语法是否正确 sudo nginx -t # 如果输出 nginx: configuration file /etc/nginx/nginx.conf test is successful 则说明语法正确 # 重载Nginx配置使新配置生效平滑重启不会断开现有连接 sudo systemctl reload nginx # 或使用 sudo nginx -s reload现在只要你将blog.yourdomain.com和project.yourdomain.com的DNS A记录都指向这台服务器的公网IP就可以通过不同的域名访问到不同的网站了。实操心得try_files指令是处理静态文件路由的神器。它的意思是“按顺序尝试这些路径直到成功为止”。$uri代表请求的URI$uri/代表将其视为目录。最后的404是保底选项。这个配置能很好地处理前端路由如Vue.js的history模式需要稍作调整try_files $uri $uri/ /index.html;。4.2 场景二部署动态应用以Node.js为例现在需求升级了我们的博客换成了Node.js驱动的动态应用比如一个Ghost博客运行在2368端口项目主页是一个Vue.js构建的单页应用SPA需要Nginx代理其API请求到后端的3000端口。第一步准备应用与目录假设Node.js博客应用已安装在/var/www/node-blog并通过PM2等进程管理器运行在localhost:2368。Vue项目构建后的静态文件在/var/www/vue-project/dist其Node.js后端API运行在localhost:3000。第二步配置Node.js博客反向代理创建/etc/nginx/conf.d/node-blog.confserver { listen 80; server_name blog.yourdomain.com; # 重要的安全与性能头设置可以放在snippets中复用 # include /etc/nginx/snippets/security-headers.conf; location / { # 将请求代理到本地的Node.js应用 proxy_pass http://127.0.0.1:2368; # 以下是一组标准的反向代理配置用于正确传递客户端信息 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; # 传递原始Host头 proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 传递经过的代理IP链 proxy_set_header X-Forwarded-Proto $scheme; # 传递原始协议http/https proxy_cache_bypass $http_upgrade; # 代理超时设置根据应用调整 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # 单独处理静态文件由Nginx直接提供效率更高 location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)$ { root /var/www/node-blog/content; # Ghost静态文件路径根据实际调整 expires 30d; access_log off; } access_log /var/log/nginx/node-blog_access.log; error_log /var/log/nginx/node-blog_error.log; }第三步配置Vue.js SPA及其API代理创建/etc/nginx/conf.d/vue-project.confserver { listen 80; server_name project.yourdomain.com; root /var/www/vue-project/dist; # Vue项目构建产物的目录 index index.html; # 处理Vue Router的history模式 location / { try_files $uri $uri/ /index.html; } # 将所有以 /api/ 开头的请求代理到后端API服务 location /api/ { # 注意proxy_pass末尾的斜杠。有斜杠表示将/api/从URI中去除后再转发。 # 例如请求 /api/users 会被转发到 http://127.0.0.1:3000/users proxy_pass http://127.0.0.1:3000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; 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_cache_bypass $http_upgrade; } # 代理WebSocket连接如果应用需要 location /ws/ { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection Upgrade; proxy_set_header Host $host; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; access_log off; } access_log /var/log/nginx/vue-project_access.log; error_log /var/log/nginx/vue-project_error.log; }配置完成后同样执行sudo nginx -t和sudo systemctl reload nginx。踩坑记录proxy_pass末尾的斜杠是初学者最容易出错的地方之一。规则是如果proxy_pass的URL包含路径如http://backend/api/则Nginx会将location匹配到的部分从原始请求URI中删除然后加上代理URL的路径。如果proxy_pass只有主机和端口如http://backend:3000则会将完整的请求URI传递过去。务必根据后端API的期望路径来仔细配置。5. 进阶配置启用HTTPS与SSL证书管理如今HTTPS已是网站标配。它不仅加密数据也是浏览器信任和SEO排名的因素。我们将使用Let‘s Encrypt提供的免费SSL证书并通过Certbot工具自动化管理。5.1 使用Certbot获取并安装证书首先安装Certbot和Nginx插件# Ubuntu sudo apt update sudo apt install certbot python3-certbot-nginx # CentOS (需要先启用EPEL) sudo yum install epel-release sudo yum install certbot python3-certbot-nginx单域名证书申请为blog.yourdomain.com申请证书。sudo certbot --nginx -d blog.yourdomain.com按照交互提示操作输入邮箱、同意协议等Certbot会自动修改你的Nginx配置文件添加SSL相关指令并设置重定向。多域名/通配符证书申请如果你想用一个证书覆盖主域名和其所有子域名如yourdomain.com,www.yourdomain.com,blog.yourdomain.com可以申请通配符证书。这需要DNS验证。sudo certbot certonly --manual --preferred-challengesdns -d *.yourdomain.com -d yourdomain.com此命令会要求你在域名DNS管理处添加一条TXT记录以验证所有权。验证通过后证书会保存在/etc/letsencrypt/live/yourdomain.com/目录下。5.2 配置Nginx强制HTTPS与优化Certbot自动生成的配置通常不错但我们可以手动优化一下。一个强化后的HTTPSserver块配置示例server { listen 443 ssl http2; # 启用HTTP/2提升性能 listen [::]:443 ssl http2; server_name blog.yourdomain.com; # 指定证书路径 ssl_certificate /etc/letsencrypt/live/blog.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.yourdomain.com/privkey.pem; # SSL优化配置可放入snippets/ssl.conf复用 ssl_protocols TLSv1.2 TLSv1.3; # 禁用不安全的TLS 1.0/1.1 ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; # 启用OCSP Stapling提高SSL握手速度 ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid300s; resolver_timeout 5s; # 安全头 add_header Strict-Transport-Security max-age63072000; includeSubDomains; preload always; add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header Referrer-Policy strict-origin-when-cross-origin always; # ... 其余的location配置与HTTP版本相同 ... root /var/www/node-blog; location / { proxy_pass http://127.0.0.1:2368; # ... proxy_set_header 等配置 ... } } # HTTP强制跳转HTTPS server { listen 80; listen [::]:80; server_name blog.yourdomain.com; # 返回301永久重定向到HTTPS版本 return 301 https://$server_name$request_uri; }5.3 证书自动续期Let‘s Encrypt证书有效期为90天。Certbot安装时会自动创建一个定时任务cron job或systemd timer来续期。你可以手动测试续期sudo certbot renew --dry-run如果测试成功就无需担心过期问题。你也可以将续期命令加入自己的监控系统。重要提示配置SSL后务必使用 SSL Labs Server Test 等工具测试你的服务器SSL配置等级确保达到A或A评级。6. 运维、调试与深度优化6.1 日志管理与问题排查清晰的日志是运维的生命线。Nginx默认的访问日志格式可能信息不全建议自定义。在nginx.conf的http块中定义日志格式http { log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for rt$request_time uct$upstream_connect_time uht$upstream_header_time urt$upstream_response_time; access_log /var/log/nginx/access.log main; }然后在各个server块中使用access_log /path/to/log main;。这个格式包含了上游响应时间对排查反向代理性能问题非常有用。常用排查命令sudo tail -f /var/log/nginx/error.log实时查看错误日志。sudo tail -f /var/log/nginx/blog_access.log实时查看特定网站的访问日志。sudo nginx -T打印出Nginx读取的所有配置内容用于检查最终生效的配置。curl -I http://yourdomain.com查看HTTP响应头。对于HTTPSopenssl s_client -connect blog.yourdomain.com:443 -servername blog.yourdomain.com检查证书详情。6.2 性能与安全优化要点连接数优化在nginx.conf的events块调整worker_connections每个worker进程可处理的最大连接数通常设置为1024或更高需结合系统ulimit -n的设置。缓冲区优化调整client_body_buffer_size,client_max_body_size特别是需要上传文件的应用以及proxy_buffer_size和proxy_buffers用于反向代理。静态文件缓存如之前示例对图片、CSS、JS等设置长期缓存并添加immutable属性避免不必要的请求。Gzip压缩在nginx.conf的http块中启用压缩文本内容节省带宽。gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xmlrss application/json;安全加固禁用不必要的HTTP方法if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 405; }隐藏Nginx版本号在nginx.conf的http块设置server_tokens off;设置X-Content-Type-Options,X-Frame-Options,Content-Security-Policy等安全头。6.3 使用Docker简化部署可选如果你熟悉Docker可以用Docker Compose来管理多个网站更加隔离和便捷。每个网站可以是一个独立的服务通过一个统一的Nginx容器作为入口网关。示例docker-compose.yml片段version: 3.8 services: nginx-proxy: image: nginx:alpine container_name: nginx-proxy ports: - 80:80 - 443:443 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro # 挂载网站配置 - ./nginx/ssl:/etc/nginx/ssl:ro # 挂载SSL证书 - ./static-site:/var/www/static-site:ro # 挂载静态网站A文件 - ./log:/var/log/nginx networks: - web-network node-blog: image: your-node-blog-image container_name: node-blog-app expose: - 2368 networks: - web-network # ... 其他配置如环境变量、数据卷等 vue-backend: image: your-vue-backend-image container_name: vue-backend-api expose: - 3000 networks: - web-network networks: web-network: driver: bridge在这种架构下Nginx容器内的配置文件中proxy_pass的地址就可以使用Docker服务名如http://node-blog:2368Docker的内部DNS会自动解析。7. 常见问题与故障排查实录即使按照步骤操作也难免会遇到问题。这里记录几个我高频遇到的坑及其解决方案。问题1访问域名显示“502 Bad Gateway”或“504 Gateway Time-out”原因分析这通常是Nginx无法连接到上游服务如你的Node.js、PHP-FPM进程。502是连接被拒绝504是连接超时。排查步骤检查后端服务是否在运行sudo systemctl status your-service或ps aux | grep node。检查后端服务监听的端口是否正确是否只监听在127.0.0.1localhost而不是0.0.0.0。Nginx容器化部署时后端服务需监听0.0.0.0。检查防火墙firewalld, ufw或安全组云服务器是否放行了后端服务的端口。查看Nginx错误日志error.log通常会有更具体的连接失败信息。增大proxy_connect_timeout,proxy_send_timeout,proxy_read_timeout的值。问题2配置重载后新网站不生效还是访问到默认页或错误页原因分析可能是配置语法错误导致Nginx没有加载新配置或者DNS缓存、浏览器缓存。排查步骤首要步骤运行sudo nginx -t确保语法检查通过。错误信息会精确到行号。确认配置文件确实在Nginx加载的目录内如/etc/nginx/conf.d/并且主配置文件nginx.conf中有include指令包含该目录。执行sudo systemctl reload nginx后用sudo systemctl status nginx确认服务状态是active (running)。在服务器本地用curl -H Host: blog.yourdomain.com http://127.0.0.1测试绕过DNS和浏览器缓存。如果本地curl成功而外网失败问题在DNS或网络。问题3静态资源CSS, JS, 图片加载404原因分析root指令路径错误或文件权限问题。排查步骤检查Nginx配置中root指定的目录路径是否正确以及该目录下是否存在请求的文件。检查文件和目录的权限。Nginx工作进程通常是nginx或www-data用户必须有该目录和文件的读取(r)和执行(x针对目录)权限。可以用ls -la /path/to/your/root查看。检查location块是否匹配。例如请求/static/css/style.css但你的location ~* \.(css)$块可能因为优先级被其他location块覆盖。问题4SSL证书配置后浏览器提示“不安全”原因分析证书链不完整、证书与域名不匹配、或服务器密码套件配置过时。排查步骤使用openssl s_client -connect ...或在线SSL检查工具查看证书详情。确认Nginx配置中ssl_certificate指向的是包含完整证书链的fullchain.pem文件而不是单独的cert.pem。确认server_name与证书申请的域名完全一致。确保配置中禁用了不安全的协议如SSLv3和加密套件。问题5如何设置一个“默认”或“兜底”网站场景当访问的域名没有匹配任何server_name时你想展示一个默认页面比如企业维护页面或者直接拒绝连接。解决方案在配置文件的最前面因为Nginx按顺序匹配第一个server块添加一个监听默认IP和端口的server块。server { listen 80 default_server; listen [::]:80 default_server; server_name _; # 通配符匹配所有未定义的域名 # 返回444状态码Nginx会直接关闭连接不发送任何响应体节省带宽。 return 444; # 或者显示一个自定义的默认页面 # root /var/www/default; # index index.html; }对于HTTPS也需要一个类似的default_server块监听443端口并配置一个通用的或通配符证书。掌握单服务器多网站部署是Web运维的基石。这套流程和配置思路具有极强的通用性稍加修改就能适配Python Flask、Java Spring Boot、Go Gin等各种后端框架。关键在于理解Nginx作为“流量路由器”的角色以及server_name、location、proxy_pass这几个核心指令的用法。在实际操作中耐心查看日志、善用nginx -t测试、循序渐进地增加配置复杂度是避免陷入调试泥潭的最好方法。