【前情提要】 图片、代码块、TOC 都搞定了,博客终于“有个人样”了。老周说:“明天让 Nginx 接管,把 hugo server 换成正经的 80 端口。”小林打开 Nginx 文档,准备配置。但他不知道,try_files、root 路径、权限,每一个都可能让他折腾一下午。

一、hugo server 不是给外人看的

早上,小林照常打开 SSH,敲下 hugo server,准备继续写文章。

老周路过,看了一眼:“你天天挂个 hugo server,干嘛呢?”

“写文章啊,本地预览。”

“那你写完了,怎么给同事看?”

小林愣了一下:“……把 HTML 发给他?”

老周叹了口气:“你 hugo server 是开发服务器,绑定在 127.0.0.1:1313,只有你自己能访问。同事访问不了,浏览器刷新一下还可能断。这是开发用的,不是生产用的。”

“那生产用啥?”

“Nginx。”老周说,“你 hugo 命令把 Markdown 生成 HTML,然后用 Nginx 托管这些 HTML。Nginx 监听 80 端口,谁都能访问。这才是正经部署。”

“那我 Mac 怎么访问?”

“VMware Fusion 的 NAT 模式,你在 Mac 上做端口转发,Mac 的 8080 转发到 Ubuntu 的 80。然后 Mac 浏览器访问 http://localhost:8080,就是访问 Ubuntu 的 Nginx。”

小林在笔记本上写下:

  • 开发:hugo server,127.0.0.1:1313,只有自己能看
  • 生产:hugo 生成 HTML + Nginx 托管,80 端口,谁都能看
  • Mac 访问:VMware NAT 端口转发,Mac 8080 → Ubuntu 80

二、hugo 构建:从 Markdown 到 HTML

“先别装 Nginx,先把你站点的 HTML 生成出来。”老周说。

“怎么生成?”

“hugo 命令,不加 server。”

小林退出 hugo server(按 Ctrl+C),然后敲:

cd ~/myblog
hugo

输出:

Start building sites …
hugo v0.x.x+extended linux/arm64
                  | EN | ZH
------------------+----+----+
 Pages            | 12 | 12
 Paginator pages  |  0 |  0
 Non-page files   |  0 |  0
 Static files     |  5 |  5
 Processed images |  0 |  0
 Aliases          |  0 |  0
 Sitemaps         |  1 |  1
 Cleaned          |  0 |  0

Total in 45 ms

“生成到哪了?”

“默认生成到 public/ 目录。”老周说,“你看看。”

ls public/

输出:

404.html
about/
archives/
categories/
css/
images/
index.html
js/
posts/
tags/

“看到了吗?index.html 就是首页,posts/ 下是你的文章,css/ 和 js/ 是主题的资源。这一堆就是静态网站,谁都能托管。”

“那 hugo 和 hugo server 有啥区别?”

“hugo 生成文件到 public/,生成完就退出。hugo server 生成到内存,起一个开发服务器,监听 1313,还支持热重载。”老周说,“开发用 hugo server,部署用 hugo。”

“那我以后每次写完文章,都要跑一次 hugo?”

“对。”老周说,“你写完 Markdown,跑 hugo,public/ 更新,Nginx 直接读 public/。就这么简单。”

小林记下:

  • hugo → 生成到 public/
  • hugo server → 开发服务器,热重载
  • 部署流程:写 Markdown → hugo → Nginx 托管 public/

三、安装 Nginx

“现在装 Nginx。”老周说。

小林敲:

sudo apt update
sudo apt install -y nginx

安装完成后,Nginx 自动启动。

“验证一下。”

systemctl status nginx

输出:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since ...

“active (running),说明 Nginx 在跑。”老周说,“你 curl 一下。”

curl -I http://localhost

输出:

HTTP/1.1 200 OK
Server: nginx/1.x.x
Date: ...
Content-Type: text/html

“看到 200 OK 了吗?”老周说,“Nginx 已经能用了,但它现在返回的是默认页面,不是你的博客。你得改配置,让它指向 public/。”

“那默认页面在哪?”

“/var/www/html/index.nginx-debian.html。”老周说,“Ubuntu 的 Nginx 默认站点在这个目录。你以后配自己的站点,就不用管它了。”

小林打开浏览器,在 Ubuntu 里访问 http://localhost,看到了 Nginx 的默认页:

Welcome to nginx!

“这个页面挺经典的。”小林说。

“经典归经典,但你得换成你的博客。”老周说,“Nginx 的配置文件在 /etc/nginx/。主配置是 nginx.conf,站点配置在 sites-available/ 和 sites-enabled/。”

“为啥有两个目录?”

“sites-available 是‘可用站点’,你写配置放这里。sites-enabled 是‘启用站点’,里面是符号链接,指向 sites-available 里的文件。只有 sites-enabled 里的配置才会生效。”老周说,“这样你可以随时启用/禁用某个站点,不用删配置。”

小林记下:

  • /etc/nginx/nginx.conf:主配置
  • /etc/nginx/sites-available/:可用站点配置
  • /etc/nginx/sites-enabled/:启用的站点(符号链接)
  • 默认站点:/var/www/html/

四、配置 Nginx 指向博客

“现在写配置。”老周说,“在 sites-available/ 下建一个 blog 文件。”

sudo nano /etc/nginx/sites-available/blog

内容:

server {
    listen 80;
    server_name localhost;

    root /home/devops/myblog/public;
    index index.html;

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

“一行一行解释。”老周说。

  • listen 80;:监听 80 端口。HTTP 默认端口。
  • server_name localhost;:域名。你本地测试用 localhost,以后有域名就改成域名。
  • root /home/devops/myblog/public;:网站根目录,指向 Hugo 生成的 public/。
  • index index.html;:默认首页文件。
  • location / { ... }:匹配所有请求。
  • try_files $uri $uri/ =404;:先找文件,再找目录,都没有返回 404。

“try_files 是 Nginx 最常用的指令之一。”老周说,“$uri 是请求的路径,比如 /posts/my-first-post/。Nginx 先找 /home/devops/myblog/public/posts/my-first-post/ 这个文件,找不到就找同名目录,再找不到就返回 404。”

“那为啥不直接 root 加 index 就够了?”

“因为 Hugo 生成的 URL 是 /posts/my-first-post/,实际文件是 public/posts/my-first-post/index.html。Nginx 默认会找目录下的 index.html,但如果你没写 try_files,某些情况下会 403 或 404。try_files $uri $uri/ =404; 是静态站点的标准写法,记住就行。”

小林保存,退出。

“现在启用站点。”老周说。

sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/

“ln -s 创建符号链接,相当于 Windows 的快捷方式。”老周说,“现在 sites-enabled/ 里有了 blog,Nginx 会加载它。”

“那默认站点呢?”

“默认站点是 sites-enabled/default,它也在监听 80 端口。你现在有两个站点监听 80,会冲突。”老周说,“删掉默认站点的链接。”

sudo rm /etc/nginx/sites-enabled/default

“现在测试配置。”

sudo nginx -t

输出:

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

“nginx -t 是 Nginx 的‘语法检查’,你每次改配置都要跑一遍。配置写错了,Nginx 重启会失败,服务直接挂。”老周说,“测试通过,再重启。”

sudo systemctl reload nginx

“reload 是平滑重启,不中断现有连接。restart 是暴力重启,会断连接。生产环境用 reload,本地随意。”老周说。

“现在访问。”

小林在 Ubuntu 浏览器里访问 http://localhost。

博客出来了。

“成了。”小林兴奋地说。

五、权限问题:Nginx 读不了你的文件

小林兴奋了没三秒,页面变成:

403 Forbidden

“啥?”小林刷新了一下。还是 403。

“403 是权限问题。”老周说,“Nginx 以 www-data 用户运行,你的 public/ 目录在 /home/devops/ 下。/home/devops/ 默认权限是 750,www-data 进不去。”

“那怎么办?”

“两种办法。”老周竖起两根手指。

办法一:把 public/ 放到 /var/www/ 下。

sudo mkdir -p /var/www/blog
sudo cp -r ~/myblog/public/* /var/www/blog/
sudo chown -R www-data:www-data /var/www/blog

“然后改 Nginx 配置,root 指向 /var/www/blog。”

办法二:改 /home/devops/ 权限。

chmod 755 /home/devops

“但这样你的家目录别人也能看了,不太安全。”老周说,“推荐办法一,把网站文件放到 /var/www/ 下,这是 Linux 的标准网站目录。”

小林选了办法一。

“但这样你每次 hugo 生成后,都要 cp 到 /var/www/blog/,太麻烦。”老周说,“有两个优化方案。”

方案一:hugo 直接生成到 /var/www/blog/。

hugo -d /var/www/blog

“-d 指定输出目录。这样 hugo 直接生成到网站目录,不用 cp。”

方案二:把 public/ 软链接到 /var/www/blog/。

sudo rm -rf /var/www/blog
sudo ln -s /home/devops/myblog/public /var/www/blog

“但这样 Nginx 还是得穿过 /home/devops/,权限问题还在。”老周说,“所以方案一最干净。”

小林用方案一:

hugo -d /var/www/blog
sudo chown -R www-data:www-data /var/www/blog

刷新,博客出来了。

“记住了。”老周说,“Nginx 以 www-data 运行,网站文件要么放 /var/www/,要么确保 www-data 能读。 你以后遇到 403,第一件事就是查权限。”

小林在笔记本上写下:

  • Nginx 运行用户:www-data
  • 标准网站目录:/var/www/
  • 403 排查:ls -l 看权限,chown www-data:www-data 改所有者
  • hugo -d /var/www/blog 直接生成到网站目录

六、Mac 访问:NAT 端口转发

“Ubuntu 里能访问了。”小林说,“但我 Mac 上怎么访问?”

“VMware Fusion 的 NAT 模式,你得做端口转发。”老周说。

老周让小林打开 VMware Fusion,找到虚拟机设置:

网络适配器 → 高级 → 端口转发

添加一条规则:

协议:TCP
主机端口:8080
虚拟机端口:80

“保存,然后 Mac 浏览器访问 http://localhost:8080。”

小林打开 Mac 的 Safari,输入 http://localhost:8080。

博客出来了。

“成了!”小林兴奋地说,“我 Mac 也能访问了。”

“这就是 NAT + 端口转发的效果。”老周说,“Mac 的 8080 转发到 Ubuntu 的 80,你访问 Mac 的 8080,实际访问的是 Ubuntu 的 Nginx。”

“那以后我同事要看呢?”

“你同事得能访问你的 Mac。”老周说,“你现在是本地虚拟机,只有你自己能看。想让同事看,要么部署到公司服务器,要么用内网穿透。但那是以后的事,你现在先把本地跑通。”

小林记下:

  • VMware Fusion NAT 端口转发:Mac 8080 → Ubuntu 80
  • Mac 访问:http://localhost:8080
  • 仅限本机,同事访问需要部署到服务器或内网穿透

七、排错挑战:502 Bad Gateway

下午,小林改了一下 Nginx 配置,想试试 try_files 的其他写法。他把:

try_files $uri $uri/ =404;

改成了:

try_files $uri $uri/ /index.html;

保存,nginx -t,通过,reload。

刷新页面。

502 Bad Gateway

“502?”小林懵了,“我刚才还是 200 呢。”

他检查 Nginx 状态:

systemctl status nginx

输出:

Active: active (running)

“Nginx 在跑啊。”

他检查错误日志:

sudo tail -20 /var/log/nginx/error.log

输出:

connect() failed (111: Connection refused) while connecting to upstream

“upstream?”小林不懂,“我又没配反向代理,哪来的 upstream?”

他喊来老周。

老周看了一眼配置,笑了:“你把 try_files 改成 /index.html,Nginx 找不到文件时,会内部重定向到 /index.html。但你的 root 是 /var/www/blog,/index.html 存在,应该没问题啊。”

他又看了一眼:“等等,你 root 写的是啥?”

小林打开配置:

root /var/www/blog;

“没错啊。”

老周仔细看:“你 try_files 最后写了 /index.html,但你没写 location = /index.html 的规则。Nginx 内部重定向后,又匹配到 location /,又走 try_files,又重定向到 /index.html,死循环了。”

“那为啥是 502?”

“因为 Nginx 检测到循环,报了 500 或 502。你日志里 connect() failed 是因为 Nginx 尝试连接 upstream,但你没有 upstream,它把 localhost:80 当成了 upstream,自己连自己,连不上。”

“那怎么改?”

“改回标准写法。”

try_files $uri $uri/ =404;

“或者,如果你想用 SPA 的写法(所有路径都返回 index.html),得加 location 规则:”

location / {
    try_files $uri $uri/ /index.html;
}

location = /index.html {
    internal;
}

“但你现在是静态博客,不需要 SPA 写法。标准写法 try_files $uri $uri/ =404; 就够了。”

小林改回去,nginx -t,reload,刷新。

博客回来了。

排错思路总结:

  1. 502 先看 Nginx 错误日志:sudo tail -20 /var/log/nginx/error.log。
  2. 如果日志显示 connect() failed,检查是否有 upstream 配置。
  3. 如果没有 upstream,检查 try_files 是否导致内部重定向死循环。
  4. 静态博客的标准写法:try_files $uri $uri/ =404;。
  5. SPA 写法需要额外 location = /index.html { internal; },但静态博客不需要。
  6. 每次改配置,先 nginx -t,再 reload。

“记住,”老周说,“try_files 最后一项,要么是 =404,要么是一个真实存在的文件。 你写 /index.html,如果没有对应的 location 规则,就会循环。你以后遇到 502,先看 error.log,再看配置。”

八、练习

  1. 练习一:在 Ubuntu 里安装 Nginx,创建 /etc/nginx/sites-available/blog,配置 root 指向 /var/www/blog,try_files $uri $uri/ =404;。启用站点,禁用默认站点,nginx -t 通过后 reload。用 curl -I http://localhost 确认返回 200。
  2. 练习二:用 hugo -d /var/www/blog 生成静态文件,chown -R www-data:www-data /var/www/blog,确认 Nginx 能正常访问博客。如果出现 403,用 ls -l 检查权限并修复。
  3. 练习三:在 VMware Fusion 中配置 NAT 端口转发,Mac 的 8080 转发到 Ubuntu 的 80。在 Mac 浏览器访问 http://localhost:8080,确认能看到博客。

参考答案

练习一:

sudo apt update
sudo apt install -y nginx

sudo nano /etc/nginx/sites-available/blog

内容:

server {
    listen 80;
    server_name localhost;
    root /var/www/blog;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
curl -I http://localhost

输出应包含 HTTP/1.1 200 OK。

练习二:

hugo -d /var/www/blog
sudo chown -R www-data:www-data /var/www/blog
sudo chmod -R 755 /var/www/blog

如果出现 403:

ls -l /var/www/blog
ls -ld /var/www /var/www/blog

确保 www-data 有读权限,目录权限为 755,文件权限为 644。

练习三:

VMware Fusion 设置:

  • 虚拟机 → 设置 → 网络适配器 → 高级 → 端口转发
  • 协议:TCP
  • 主机端口:8080
  • 虚拟机端口:80

Mac 浏览器访问 http://localhost:8080,确认博客显示。

九、下集预告

Nginx 配好了,博客能访问了,Mac 也能通过端口转发看到了。老周说:“博客上线了,但你不知道它什么时候会挂。”小林打开 access.log,准备看看日志。但他不知道,grep、awk、状态码统计,正在前面等着他。

下一集:《日志文件在哪?Nginx 开始说话了》