Nginx虚拟主机:在一台服务器上托管多个网站

解锁Nginx虚拟主机(服务器块)的强大功能,高效地在单台服务器上托管多个网站或子域名。本指南提供全面的分步教程,涵盖目录设置、配置文件创建、启用服务器块和Nginx测试。学习子域名、默认服务器块、HTTPS集成和专用日志记录的最佳实践。通过实用示例和基本故障排除技巧,掌握多站点Nginx托管,优化资源使用并简化Web服务器管理。

Nginx虚拟主机:在一台服务器上托管多个网站

在一台服务器上运行几个小站点是Nginx的常规任务。你可能有一个公司网站、一个文档站点、一个临时应用和一个客户门户,全部部署在同一台机器上。Nginx通过server块将它们分开。Apache用户通常称同样的概念为虚拟主机,因此你会在教程和托管面板中看到这两个术语。

关键部分很简单:Nginx查看端口、IP地址和Host头,然后选择匹配的server块。如果加载了错误的站点,问题通常并不神秘。通常是缺少DNS记录、server_name中的拼写错误、默认服务器捕获了请求,或者两个文件声称同名。

理解Nginx服务器块(虚拟主机)

核心上,Nginx的服务器块是在Nginx配置文件(nginx.conf或包含的文件)中定义的配置指令。每个server块定义特定虚拟主机的配置,指示Nginx如何响应特定域名或域名集合的请求。Nginx使用listen指令指定要监听的IP地址和端口,使用server_name指令标识此服务器块应响应的域名或主机名。

当请求到达时,Nginx检查HTTP请求的Host头,并将其与配置的服务器块的server_name指令进行比较。然后,它提供匹配服务器块中定义的内容。如果没有server_name匹配,Nginx通常会回退到默认服务器块(第一个server块或明确标记为default_server的块)。

前提条件

开始之前,请确保满足以下条件:

  1. 已安装Nginx:Nginx应已安装并在服务器上运行。如果没有,通常可以通过系统的包管理器安装(例如,Ubuntu/Debian上使用sudo apt update && sudo apt install nginx,CentOS/RHEL上使用sudo yum install nginx)。
  2. 域名:你需要至少两个域名(例如,example1.comexample2.com)或子域名(例如,blog.example.comapp.example.com)来托管。这些域名的DNS A/AAAA记录必须指向你服务器的公共IP地址。
  3. 基本目录结构:规划网站文件存放的位置。常见做法是/var/www/yourdomain.com/html
  4. Sudo权限:你需要sudo访问权限来修改Nginx配置文件。

分步设置指南

让我们设置两个虚拟主机:example1.comexample2.com

步骤1:为网站创建目录结构

首先,为每个网站创建根目录。这些目录将存储它们的HTML、CSS、JavaScript和其他静态文件。常见位置是/var/www/

sudo mkdir -p /var/www/example1.com/html
sudo mkdir -p /var/www/example2.com/html

# 将所有权设置为你的用户(将$USER替换为你的用户名)以允许编辑
sudo chown -R $USER:$USER /var/www/example1.com/html
sudo chown -R $USER:$USER /var/www/example2.com/html

# 为Web服务器设置读取权限
sudo chmod -R 755 /var/www

接下来,在每个目录中创建一个简单的index.html文件以测试设置:

对于/var/www/example1.com/html/index.html

<!-- /var/www/example1.com/html/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>欢迎来到Example1.com!</title>
</head>
<body>
    <h1>成功!这是Example1.com。</h1>
    <p>此虚拟主机工作正常。</p>
</body>
</html>

对于/var/www/example2.com/html/index.html

<!-- /var/www/example2.com/html/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>欢迎来到Example2.com!</title>
</head>
<body>
    <h1>成功!这是Example2.com。</h1>
    <p>此虚拟主机也在工作!</p>
</body>
</html>

步骤2:创建Nginx服务器块配置文件

Nginx通常从/etc/nginx/sites-enabled/目录中的文件加载服务器块配置。这些文件通常是存储在/etc/nginx/sites-available/中的配置的符号链接。这种分离允许你存储尚未激活的配置,或轻松启用/禁用站点。

example1.com创建一个新的配置文件:

sudo nano /etc/nginx/sites-available/example1.com.conf

添加以下内容:

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

    root /var/www/example1.com/html;
    index index.html index.htm index.nginx-debian.html;

    server_name example1.com www.example1.com;

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

    access_log /var/log/nginx/example1.com_access.log;
    error_log /var/log/nginx/example1.com_error.log;
}

指令说明:

  • listen 80;:Nginx监听80端口(标准HTTP)。listen [::]:80;用于IPv6。
  • root /var/www/example1.com/html;:指定此服务器块的文档根目录。Nginx将在此目录中查找文件。
  • index index.html ...;:定义请求目录时Nginx应提供的默认文件(例如,当有人访问example1.com/时)。
  • server_name example1.com www.example1.com;:这很关键。它告诉Nginx使用此服务器块的配置响应example1.comwww.example1.com的请求。
  • location / { ... }:定义如何处理特定URI请求的块。try_files尝试直接提供文件($uri),然后是目录($uri/),最后返回404 Not Found错误。
  • access_logerror_log:为此特定站点指定单独的日志文件,这是便于调试和分析的良好实践。

现在,为example2.com创建一个类似的配置文件:

sudo nano /etc/nginx/sites-available/example2.com.conf

添加以下内容:

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

    root /var/www/example2.com/html;
    index index.html index.htm index.nginx-debian.html;

    server_name example2.com www.example2.com;

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

    access_log /var/log/nginx/example2.com_access.log;
    error_log /var/log/nginx/example2.com_error.log;
}

步骤3:启用服务器块

要启用这些配置,从sites-available目录创建符号链接到sites-enabled目录。这告诉Nginx在启动时包含这些文件。

sudo ln -s /etc/nginx/sites-available/example1.com.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example2.com.conf /etc/nginx/sites-enabled/

步骤4:测试Nginx配置

在重新加载之前,测试Nginx配置是否存在语法错误至关重要。这可以防止由于拼写错误导致Nginx重启失败。

sudo nginx -t

你应该看到类似以下的输出,表示成功:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

如果看到任何错误,请在相应的配置文件中修复它们,并重新运行sudo nginx -t直到通过。

步骤5:重启Nginx

通过重启或重新加载Nginx来应用新配置。reload通常更可取,因为它允许Nginx加载新配置而不中断活动连接。

sudo systemctl reload nginx
# 或者,如果reload不起作用或对于全新安装:
sudo systemctl restart nginx

步骤6:更新DNS记录

确保example1.comwww.example1.comexample2.comwww.example2.com的DNS A记录都指向你的Nginx服务器的IP地址。如果没有正确的DNS条目,你的浏览器将不知道在哪里找到你的网站。

一旦DNS传播完成(可能需要几分钟到几小时),你应该能够在Web浏览器中访问http://example1.comhttp://example2.com,并看到相应的index.html页面。

高级场景和最佳实践

托管子域名

托管子域名(例如,blog.example.comshop.example.com)的工作方式与托管独立域名完全相同。只需使用子域名作为server_name定义一个新的服务器块。

blog.example.com的示例:

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

    root /var/www/blog.example.com/html;
    index index.html;

    server_name blog.example.com;

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

记得创建目录(/var/www/blog.example.com/html)、创建index.html、创建符号链接并重新加载Nginx。

默认服务器块

拥有一个默认服务器块是一个好习惯,它可以捕获与服务器上任何其他server_name指令不匹配的域名请求。这可以防止未知请求被Nginx找到的“第一个”虚拟主机提供,或者允许你提供通用的“站点未找到”页面。

通常,nginx.confsites-enabled中的第一个server块隐式地是默认的。你可以使用default_server显式设置一个:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _;
    # 下划线`_`是一个不存在的域名,永远不会匹配真实请求。
    # 你也可以使用localhost。

    root /var/www/default_site/html;
    index index.html;

    location / {
        return 444; # 对未知主机返回Nginx特定的444错误(无响应)
        # 或者,提供通用着陆页:
        # try_files $uri $uri/ =404;
    }
}

警告:如果你定义了一个default_server块,确保在给定的listen端口上只有一个server块具有default_server标志,否则Nginx会记录警告。

使用HTTPS(SSL/TLS)保护虚拟主机

对于生产网站,启用HTTPS是必不可少的。这涉及获取SSL/TLS证书(例如,通过使用Certbot的Let's Encrypt)并配置Nginx在端口443上监听并附带证书。

一个典型的HTTPS服务器块如下所示(获取证书后):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example1.com www.example1.com;

    root /var/www/example1.com/html;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/example1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example1.com/privkey.pem;

    # 包含其他SSL配置(密码、协议等)
    include /etc/nginx/snippets/ssl-params.conf;

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

# 可选:此域名的HTTP到HTTPS重定向
server {
    listen 80;
    listen [::]:80;
    server_name example1.com www.example1.com;
    return 301 https://$host$request_uri;
}

通常有一个单独的HTTP服务器块,其唯一目的是将所有流量重定向到其HTTPS对应项。

如果你使用Certbot,它可能会为你创建或编辑这些块。这很方便,但你仍然应该阅读生成的文件。自动化证书工具有时会在你本不会选择的位置添加重定向逻辑,重复的重定向会使故障排除更加困难。

每个站点的日志记录

如示例所示,为每个虚拟主机分配单独的access_logerror_log文件是一种最佳实践。这使得调试问题和分析单个网站的流量变得更容易,而无需筛选合并的日志。

配置文件结构

对于较大的部署,考虑按如下方式组织你的Nginx配置文件:

  • nginx.conf:主配置,包含conf.d/*.confsites-enabled/*
  • conf.d/:通用的服务器范围设置(例如,Gzip、缓存)。
  • snippets/:可重用的Nginx配置片段(例如,SSL参数、常见的location块)。
  • sites-available/:每个网站的单独server块。
  • sites-enabled/:指向sites-available/中活动配置的符号链接。

常见问题故障排除

  • 403 Forbidden错误:这通常意味着Nginx没有读取网站文件或目录的权限。仔细检查文件和目录权限(例如,sudo chmod -R 755 /var/www/yourdomain.com/html,并确保Nginx用户(通常是www-datanginx)可以读取它们)。
  • 404 Not Found错误:验证服务器块中的root指令是否指向正确的目录,并且你的index.html文件存在于该位置。另外,确保try_files配置正确。
  • 加载了错误的站点:这通常表示server_name指令有问题。确保server_name与你尝试访问的域名完全匹配(包括www.或子域名)。同时,检查你的DNS记录。
  • Nginx无法启动/重新加载:在尝试重新加载或重启Nginx之前,始终使用sudo nginx -t测试配置。错误消息将指出语法错误发生的行和文件。
  • DNS问题:如果你可以通过IP地址访问站点,但无法通过域名访问,那几乎肯定是DNS问题。使用dignslookup验证域名的A记录是否指向正确的服务器IP。

在DNS就绪前进行测试

你不必等待公共DNS来测试Nginx端。你可以发送带有自定义Host头的请求:

curl -H "Host: example1.com" http://203.0.113.10/
curl -H "Host: example2.com" http://203.0.113.10/

203.0.113.10替换为你的服务器IP。如果每个命令返回正确的测试页面,则服务器块匹配正常工作。如果两个命令返回相同的页面,检查两个文件是否已启用,server_name是否正确,以及默认块是否捕获了请求。

对于HTTPS,测试略有不同,因为TLS在处理HTTP Host头之前使用SNI:

curl --resolve example1.com:443:203.0.113.10 https://example1.com/

该命令告诉curl连接到你的服务器IP,同时仍然使用example1.com进行TLS和HTTP。这是在更改DNS之前测试新HTTPS虚拟主机的最快方法之一。

可维护的多站点模式

对于少数静态站点,上面的示例就足够了。一旦你托管多个应用程序,减少重复并仅集中真正共享的部分。例如,将常见的安全头、压缩和SSL参数放在片段中,但保持每个站点的rootserver_name、upstream和日志在其自己的文件中可见。

避免不阅读每一行就将一个大型生产块从一个域复制到另一个域。这就是server_name错误、错误的证书路径和共享日志文件潜入的方式。一个实用的审查清单很短:

  • server_name是否包含用户将键入的所有主机名?
  • rootproxy_pass是否指向此站点,而不是前一个?
  • 访问和错误日志是否足够分离以便单独调试此站点?
  • 重新加载前nginx -t是否通过?
  • curl -H "Host: ..."curl --resolve是否返回预期的站点?

最后说明

当每个站点都有清晰的服务器块、正确的server_name和可预测的默认回退时,Nginx虚拟主机是可靠的。保持文件简单。在重新加载之前测试每个更改。当站点重要时使用专用日志。一旦你能证明DNS、TLS/SNI或服务器块匹配是失败的部分,大多数多站点Nginx问题就变得容易解决了。