Nginx + HTTPS + Node.js 简易配置教程

本文于2019年1月19日有更新,添加了 systemd 守护进程的配置方法。

毕业论文写完了、答辩搞定了,回家呆一周之后就要去北京当烟酒僧了,在长春的同学该考研的考研、该忙毕业的忙毕业,大家都没时间搭理我,一个人在家好无聊……没错,我就是超级边缘人螺丝Ỏ̷͖͈̞̩͎̻̫̫̜͉̠̫͕̭̭̫̫̹̗̹͈̼̠̖͍͚̥͈̮̼͕̠̤̯̻̥̬̗̼̳̤̳̬̪̹͚̞̼̠͕̼̠̦͚̫͔̯̹͉͉̘͎͕̼̣̝͙̱̟̹̩̟̳̦̭͉̮̖̭̣̣̞̙̗̜̺̭̻̥͚͙̝̦̲̱͉͖͉̰̦͎̫̣̼͎͍̠̮͓̹̹͉̤̰̗̙͕͇͔̱͕̭͈̳̗̭͔̘̖̺̮̜̠͖̘͓̳͕̟̠̱̫̤͓͔̘̰̲͙͍͇̙͎̣̼̗̖͙̯͉̠̟͈͍͕̪͓̝̩̦̖̹̼̠̘̮͚̟͉̺̜͍͓̯̳̱̻͕̣̳͉̻̭̭̱͍̪̩̭̺͕̺̼̥̪͖̦̟͎̻̰_Ỏ̷͖͈̞̩͎̻̫̫̜͉̠̫͕̭̭̫̫̹̗̹͈̼̠̖͍͚̥͈̮̼͕̠̤̯̻̥̬̗̼̳̤̳̬̪̹͚̞̼̠͕̼̠̦͚̫͔̯̹͉͉̘͎͕̼̣̝͙̱̟̹̩̟̳̦̭͉̮̖̭̣̣̞̙̗̜̺̭̻̥͚͙̝̦̲̱͉͖͉̰̦͎̫̣̼͎͍̠̮͓̹̹͉̤̰̗̙͕͇͔̱͕̭͈̳̗̭͔̘̖̺̮̜̠͖̘͓̳͕̟̠̱̫̤͓͔̘̰̲͙͍͇̙͎̣̼̗̖͙̯͉̠̟͈͍͕̪͓̝̩̦̖̹̼̠̘̮͚̟͉̺̜͍͓̯̳̱̻͕̣̳͉̻̭̭̱͍̪̩̭̺͕̺̼̥̪͖̦̟͎̻̰。

本着打发时间的目的,给博客加了个留言板,部署程序的时候遇到了不少相当有意思的问题,所以写了这篇教程记录一下各种各样的踩坑历史d(`・∀・)b。

这篇文章将包含如下内容:使用 Nginx 作反向代理配置服务器时 Node 程序的坑点、如何为 Node 程序配置 Let’s Encrypt 的 SSL 证书、如果你打算使用 WebSocket 技术的话,需要处理的雷点。

Node.js 程序配置

获取客户端真实 IP

利用 Nginx 的反向代理功能连接外网与服务器上的 Node 程序会令其无法通过 req.connection.remoteAddress 获得客户端的真实 IP,这个时候需要修改获得 IP 的方法,使用 request.headers['X-Real-IP'],Socket.io 则可以使用 socket.handshake.headers["x-real-ip"]

同时 Nginx 的 location 块内需要增加如下语句传递真实IP:

1
2
proxy_set_header  X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

(事实上,X-Real-IPX-Forwarded-For 存在区别,本文对此不做过多说明,只是简单的使用了 X-Real-IP,有需要的读者可以自行搜索相关内容。)

程序持久化

利用Node写的程序有个特点,一旦出错服务端就会崩溃,我们肯定不能每次都手工重启它,因此需要一个持久化工具。持久化工具有很多,比如forever或者pm2,本例中介绍两种持久化工具, pm2 和 systemd,个人推荐后者。

使用 pm2 作为持久化工具

既然你用了 Node,服务器上自然是装了 npm ,我们使用 npm 安装 pm2。

1
# npm install -g pm2

安装完毕之后就可以利用 pm2 启动服务端程序了,假设入口文件是 index.js,那么我们通过这个命令持久化该程序:

1
$ pm2 start index.js

我们还可以停止某一程序,或者从 pm2 的持久化列表将它删除:

1
2
$ pm2 stop index.js
$ pm2 delete index.js

如果你想要看某一程序的崩溃日志,可以使用如下命令:

1
$ pm2 logs index.js

使用 systemd 建立守护进程

/etc/systemd/system 建立一个以 .service 为扩展名的配置文件,比如:

1
# vim /etc/systemd/system/my-node-app.service

插入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=My Node Application
After=network.target

[Service]
Environment=NODE_PORT=3000
Type=simple
User=[YOUR USER NAME]
ExecStart=/usr/bin/node [JS FILE NAME]
Restart=on-failure

[Install]
WantedBy=multi-user.target

刷新 systemd 守护进程列表:

1
sudo systemctl daemon-reload

启动你的服务:

1
sudo service [YOUR SERVICE FILE NAME] start

注意,替换方括号内的内容,不包含文件扩展名 .service

查看服务情况:

1
service [YOUR SERVICE FILE NAME] status

如果显示服务启动成功,则可以设置该服务开机自动启动:

1
sudo systemctl enable [YOUR SERVICE FILE NAME]

Nginx配置书写

注:如果你没做什么妖艳的配置的话,Nginx 的配置目录会被放在/etc/nginx/conf.d里,在里面新建一个conf文件作为单独的站点配置就可以了。

基础配置

如果你使用了自己的证书,那么可以这样设置你的 Nginx 配置文件(方括号内的内容需要手动设置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 443;
server_name [Your domain];

ssl on;
ssl_certificate [Cert files location]/fullchain.pem;
ssl_certificate_key [Cert files location]/privkey.pem;
ssl_trusted_certificate [Cert files location]/chain.pem;

location / {
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:[Nodejs application port]/;
proxy_redirect off;
}
}

server {
listen 80;
server_name [Your domain];
return 301 https://$server_name$request_uri;
}

如果你打算使用 Let’s Encrypt,则不应使用本配置模板,具体参见下一小节。

如果你使用了 WebSocket 技术

如果你使用了 WebSocket 技术,那么需要在 Nginx 配置当中设置升级你的 HTTP 协议,在 location 块内加入两行:

1
2
proxy_set_header  Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

否则客户端在尝试使用wss协议连接到服务器时,会报400错误。

配置Let’s Encrypt

如果你打算使用 Let’s Encrypt 的话,需要首先为获取证书做基本的服务端配置(在这里我们选择 webroot 的方式进行认证):

首先我们需要先建立一个目录放置域名所有权验证文件所需的文件,这里我们以占位符 [Challange folder] 代替。注意,请确保这一目录是可写的。

1
2
3
4
5
6
7
8
server {
listen 80;
server_name chat.qzworld.net;

location ^~ /.well-known {
root [Challange folder];
}
}

之后分别测试配置文件、重新启动 Nginx、安装 Certbot 并开始使用 webroot 方式获取证书(下面的命令需要逐行执行,确保每一行执行成功之后再执行下一行,我的环境是 CentOS 7 所以使用 yum,如果你是 ubuntu 的话可能需要使用 apt-get,Fedora 使用 dnf):

1
2
3
4
# nginx -t
# service nginx restart
# yum install certbot
# certbot certonly --webroot -w [Challange folder] -d [Your domain]

如果不出意外的话,你已经可以看到成功获得证书的提示信息了,输出信息当中应当会包含证书的存储地址,记下该地址以备一会填写 nginx 信息。一般的,通过 Certbot 生成的证书会被存储在 /etc/letsencrypt/live/[yourdomain]/ 或者 /etc/certbot/live/[yourdomain]/ 下。

接下来我们添加自动更新:

1
# crontab -e

在弹出的文本编辑器中,插入下面的语句,并保存退出。

1
0 0 * * 3 certbot renew --post-hook "systemctl reload nginx"

上面这条语句的意思是:每周三晚上十二点的时候为你的证书续期并重启 nginx 服务。

接下来,重新编辑你的 nginx 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
listen 443;
server_name [Your domain];

ssl on;
ssl_certificate [Cert files location]/fullchain.pem;
ssl_certificate_key [Cert files location]/privkey.pem;
ssl_trusted_certificate [Cert files location]/chain.pem;

location / {
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:[Nodejs application port]/;
proxy_redirect off;
}
}

server {
listen 80;
server_name [Your domain];

location ^~ /.well-known {
root [Challange folder];
}

location / {
return 301 https://$server_name$request_uri;
}
}

最后,使用 nginx -t 测试配置文件书写的正确性,并通过 service nginx restart 重启Nginx服务。

这样,你的应用就部署完成啦(ゝ∀・)b!

番外

http 上不去但是 https 上得去不一定是因为你的 nginx 配置文件有问题,也有可能是因为你的 IP 因为某些不可抗力【哔——】。别问我为什么要提醒你,我才不会告诉你明明上午都没问题下午我的网站用 http 就上不去了呢,哼!我更不会告诉你我为了排查这个「bug」花了一下午呢!哼!(ㆆᴗㆆ)|||