Post

【技术备忘录】Ubuntu下HTTPS网站部署记录

为Ubuntu机器上部署的Web服务配置HTTPS提高安全性的操作记录。

【技术备忘录】Ubuntu下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+wstunnelSSH over HTTPS

适合在高度审查或者安全策略严格的环境下,例如只开放了443端口但是禁止22端口的公网机器使用,可以复用443端口的HTTPS加密,规避对SSH协议的审查。 此方法利用了WebSocket协议来转发TCP连接,无需配置HTTP CONNECT,因为CaddyHTTP CONNECT的支持并不好(参考链接)。

  1. 在目标机器上安装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
  1. 在目标机器上配置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
}
  1. 在客户端机器上安装wstunnel并配置SSH的config文件:
  2. 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,其他用户不可有权限,否则无法使用。

This post is licensed under CC BY 4.0 by the author.