【前情提要】
图片、代码块、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→ Ubuntu80
二、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→ Ubuntu80 - 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,刷新。
博客回来了。
排错思路总结:
- 502 先看 Nginx 错误日志:
sudo tail -20 /var/log/nginx/error.log。 - 如果日志显示
connect() failed,检查是否有upstream配置。 - 如果没有
upstream,检查try_files是否导致内部重定向死循环。 - 静态博客的标准写法:
try_files $uri $uri/ =404;。 - SPA 写法需要额外
location = /index.html { internal; },但静态博客不需要。 - 每次改配置,先
nginx -t,再reload。
“记住,”老周说,“try_files 最后一项,要么是 =404,要么是一个真实存在的文件。 你写 /index.html,如果没有对应的 location 规则,就会循环。你以后遇到 502,先看 error.log,再看配置。”
八、练习
- 练习一:在 Ubuntu 里安装 Nginx,创建
/etc/nginx/sites-available/blog,配置root指向/var/www/blog,try_files $uri $uri/ =404;。启用站点,禁用默认站点,nginx -t通过后reload。用curl -I http://localhost确认返回 200。 - 练习二:用
hugo -d /var/www/blog生成静态文件,chown -R www-data:www-data /var/www/blog,确认 Nginx 能正常访问博客。如果出现 403,用ls -l检查权限并修复。 - 练习三:在 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 开始说话了》