【技术备忘录】Ubuntu下HTTPS网站部署记录
为Ubuntu机器上部署的Web服务配置HTTPS提高安全性的操作记录。
Caddy: 一站式HTTPS管理
我在和DeepSeek交流过程中偶然了解到除了nginx外,还有Caddy这个可以为网站部署HTTPS及反向代理的服务器应用,而且操作很简便。
官方文档:Caddy docs 官方文档中文版翻译文档:Caddy文档
安装
1
2
3
4
5
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main" | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
安装过程中会有一个要求确认的选项,直接按Y确认即可。
配置
有关Caddyfile的编写可以参阅此文档链接。
1
sudo vim /etc/caddy/Caddyfile
我采用的示例配置:
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
30
# 支持多域名绑定
<绑定到该服务器的域名1>,<绑定到该服务器的域名2> {
# 显式指定 TLS 协议版本(避免旧客户端协商失败)
tls {
protocols tls1.2 tls1.3
# ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # 这个选项一般情况下不开
# key_type 可选:ed25519, rsa2048, rsa4096, ecdsa_p256, ecdsa_p384, 这个选项一般情况下不开
}
# 反向代理到本地 13000 端口的 HTTP 服务
reverse_proxy localhost:13000 {
header_up Host {host} # 传递原始域名
header_up X-Real-IP {remote} # 传递客户端 IP
}
# 可选:强制 HTTP 跳转 HTTPS(若需要)
@http {
protocol http
}
redir @http https://{host}{uri} permanent
}
# 指定本地12345端口监听的反向代理
:12345 {
tls {
protocols tls1.2 tls1.3
# ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # 这个选项一般情况下不开
}
reverse_proxy localhost:13000
}
Caddy会自动申请、绑定、管理并续期这些证书。公签证书颁发的CA是Let's Encrypt这一免费SSL证书颁发机构,为开发者提供免费的HTTPS证书。
本地自签证书,适用内网服务器:
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
30
31
# 支持多域名绑定
<绑定到该服务器的域名1>,<绑定到该服务器的域名2> {
# 显式指定 TLS 协议版本(避免旧客户端协商失败)
# 这里使用Caddy内部CA自签证书
tls internal {
protocols tls1.2 tls1.3
# ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # 这个选项一般情况下不开
# key_type 可选:ed25519, rsa2048, rsa4096, ecdsa_p256, ecdsa_p384,这个选项一般情况下不开
}
# 反向代理到本地 13000 端口的 HTTP 服务
# 静态文件服务:当请求以 /file 开头时,直接从 /home/ubuntu/vdb/file_server 提供文件
# 放在 reverse_proxy 之前以确保静态路径不会被代理转发
handle_path /file* {
root * /home/ubuntu/vdb/file_server
file_server {
browse
}
}
reverse_proxy localhost:13000 {
header_up Host {host} # 传递原始域名
header_up X-Real-IP {remote} # 传递客户端 IP
}
# 可选:强制 HTTP 跳转 HTTPS(若需要)
@http {
protocol http
}
redir @http https://{host}{uri} permanent
}
配置完毕后,使用以下命令重载配置:
1
sudo caddy reload --config /etc/caddy/Caddyfile
如果有WARNING提示Caddyfile配置格式有误,可以用命令自动修正配置文件格式:
1
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
重载配置后,可以通过以下命令查看自动申请的TLS证书:
1
sudo ls /var/lib/caddy/.local/share/caddy/certificates/
如果使用的是自签证书,则需要使用caddy trust命令在本机信任内部CA根证书,然后在/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt下找到获取信任的CA根证书,并分发给内网其他客户端。
Caddy默认的内部CA证书的有效期是10年,注意定时更换。
Linux客户端注册CA根证书的方法
- 基本证书注册
Linux默认的系统级CA根证书存储路径有2个:
1
2
/usr/share/ca-certificates
/usr/local/share/ca-certificates
将CA根证书拷贝到客户端的以上两个任一目录下,然后执行以下命令即可注册证书:
1
sudo update-ca-certificates
然后在.bashrc或者.zshrc中添加以下内容:
1
2
3
export PATH="$PATH:/usr/sharc/ca-certificates:/usr/local/share/ca-certificates"
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
- Anaconda环境信任
如果涉及Anaconda环境的终端,使用以下命令信任(以llmapi_ca.crt为例):
1
sudo cat /usr/share/ca-certificates/llmapi_ca.crt >> /home/wfy/anaconda3/ssl/cacert.pem
- Chromium浏览器信任
如果Edge等基于Chromium的浏览器仍然不信任证书,使用以下命令更新NSS库导入解决(以llmapi_ca.crt为例):
1
2
sudo apt install libnss3-tools
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "LLMAPI CA" -i /usr/share/ca-certificates/llmapi_ca.crt
- 全局信任 编辑
/etc/ca-certificates.conf,添加一行:
1
llmapi_ca.crt
然后运行:
1
sudo update-ca-certificates --fresh
这么做有一个副作用,就是会将系统之前信任的~/.ssh/authorized_keys被清空,遇到这种情况如果手头还有私钥,可以用ssh-keygen命令重新计算公钥并信任:
1
2
3
cd ~/.ssh/
ssh-keygen -y -f ./id_rsa > id_rsa.pub
cat ./id_rsa.pub > authorized_keys
需要在云服务器提供商的防火墙及UFW中放通80和443端口才能正常申请TLS证书,否则在使用https访问的时候将出现访问失败的情况,因为CA在验证域名所有权的时候会检查目标服务器的80/443端口是否开放,如果验证失败就不会签发证书。
高级用法:基于Caddy+wstunnel的SSH over HTTPS
适合在高度审查或者安全策略严格的环境下,例如只开放了443端口但是禁止22端口的公网机器使用,可以复用443端口的HTTPS加密,规避对SSH协议的审查。 此方法利用了WebSocket协议来转发TCP连接,无需配置HTTP CONNECT,因为Caddy对HTTP CONNECT的支持并不好(参考链接)。
- 在目标机器上安装
wstunnel,并设置systemd服务启动:1 2 3 4 5 6
wget https://github.com/erebe/wstunnel/releases/download/v10.5.1/wstunnel_10.5.1_linux_amd64.tar.gz mkdir -p wstunnel tar -xzvf wstunnel_10.5.1_linux_amd64.tar.gz -C ./wstunnel cd ./wstunnel chmod +x ./wstunnel mv ./wstunnel /usr/local/bin
配置~/.config/systemd/user/wstunnel.service,以监听localhost:8081端口为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=wstunnel
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/wstunnel server ws://localhost:8081
Restart=on-failure
RestartSec=1s
TimeoutStopSec=5s
LimitNOFILE=1048576
[Install]
WantedBy=default.target
然后使能服务:
1
2
systemctl --user daemon-reload
systemctl --user enable wstunnel --now
- 在目标机器上配置
Caddyfile并重载:
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
30
31
32
33
34
35
36
<域名> {
# 显式指定 TLS 协议版本(避免旧客户端协商失败)
tls {
protocols tls1.2 tls1.3
# ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
# key_type ed25519
}
# 静态文件服务配置写这块
# SSH over WebSocket
@wstunnel {
path /ssh* # 与下面客户端wstunnel连接使用的-P选项要相同
header_regexp Connection (?i).*upgrade.*
header_regexp Upgrade (?i).*websocket.*
}
handle @wstunnel {
reverse_proxy localhost:8081 { # 这里的端口与上面wstunnel配置监听的端口要一致
header_up Host {host}
header_up X-Real-IP {remote}
transport http {
versions h1
}
flush_interval -1
}
}
# 反向代理到本地 HTTP 服务配置写这块
# 可选:强制 HTTP 跳转 HTTPS(若需要)
@http {
protocol http
}
redir @http https://{host}{uri} permanent
}
- 在客户端机器上安装
wstunnel并配置SSH的config文件: 1 2 3 4 5 6
Host host-wss HostName 127.0.0.1 Port 22 User <user_name> ProxyCommand wstunnel client -P ssh -L stdio://127.0.0.1:22 wss://{你的公网机器域名} IdentityFile ~/.ssh/id_rsa安装
wstunnel就是去Release页面下载一下最新版的可执行文件压缩包并解压到系统有索引的PATH目录下即可。 随后在客户端机器上使用ssh host-wss即可连接到目标机器,也可以使用目标机器当做跳板来进行内网机器的SSH访问。
一句话启动Python文件服务器
1
python3 -m http.server <port> --directory=<path_to_your_directory>
之后利用Caddy的反向代理即可实现远程只读访问。
如果需要实现在浏览器中预览文本文件,可以用如下方案:
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
30
31
32
33
34
35
#!/usr/bin/env python3
import http.server
import socketserver
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def guess_type(self, path):
# 先调用父类的方法获取默认MIME类型
mimetype = super().guess_type(path)
# 如果是文本文件类型,设置为文本MIME类型
if mimetype == 'application/octet-stream':
# 文本文件扩展名列表
text_extensions = {
'.txt', '.log', '.md', '.markdown', '.rst', '.toml',
'.py', '.js', '.html', '.htm', '.css', '.xml', '.json',
'.csv', '.tsv', '.yml', '.yaml', '.ini', '.cfg', '.conf',
'.sh', '.bash', '.zsh', '.php', '.rb', '.java', '.c', '.cpp',
'.h', '.hpp', '.go', '.rs', '.swift', '.kt', '.sql', '.yaml',
}
# 检查文件扩展名
import os
_, ext = os.path.splitext(path.lower())
if ext in text_extensions:
return 'text/plain'
return mimetype
if __name__ == '__main__':
BIND_IP = "0.0.0.0"
PORT = 1234
with socketserver.TCPServer((BIND_IP, PORT), CustomHTTPRequestHandler) as httpd:
print(f"服务器运行在 http://{BIND_IP}:{PORT}")
print("文本文件将在浏览器中预览而不是下载")
httpd.serve_forever()
dynv6.com + ddclient: 支持DDNS的免费域名
dynv6.com是一个提供免费三级域名的域名服务商,可以在上面快速创建自己的三级域名并且添加DNS解析记录,网站操作很简单,这里不再赘述。 在Linux下,dynv6支持通过利用ddclient来实现自动更新DDNSv6域名,实现IPv6下的纯公网域名免备案访问。
安装ddclient
ddclient的安装需要先添加PPA源,参考官方PPA源里给出的命令:
1
2
3
sudo add-apt-repository ppa:ddclient/ppa
sudo apt update
sudo apt install ddclient -y
配置ddclient
可以参考ddclient文档进行配置。支持绑定某个网卡并自动获取其上的IP并推送到dynv6。使用的配置文件/etc/ddclient.conf如下,其中的password字段需要参考dynv6官网你的域名Zone的Instructions选项卡下的内容,每个账户的password值是恒定的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf
protocol=dyndns2 \
usev4=ifv4, if=enp3s0 \
usev6=ifv6, if=wlp4s0 \
server=dynv6.com \
ssl=yes \
login=none \
password='<password>' \
xxx.dynv6.net
protocol=dyndns2 \
usev4=ifv4, if=wlp4s0 \
usev6=ifv6, if=wlp4s0 \
server=dynv6.com \
ssl=yes
login=none \
password='<password>' \
xxx.dynv6.net
编辑完成后保存,使用sudo ddclient手动更新,但是在ddclient安装完毕后会启动一个每5分钟自动更新的服务,所以一般不太需要手动更新。
ddclient只支持使用sudo权限执行,且
/etc/ddclient.conf的权限配置必需为600,其他用户不可有权限,否则无法使用。
