Traefik vs Caddy vs Nginx:Docker反向代理对比

3 分钟阅读·Matthieu·docker-composelets-encryptreverse-proxynginxcaddytraefikdocker|

三套可用的Docker Compose配置,分别使用Traefik、Caddy和Nginx作为VPS上的反向代理。相同后端、真实基准测试,以及帮你做出选择的决策框架。

你的VPS上跑着Docker容器,需要HTTPS、按域名路由和统一入口。Traefik、Caddy和Nginx都能解决这个问题,但方式各不相同。

本文提供三套可用的Docker Compose配置,用同一个后端分别部署在三个代理后面。复制适合你场景的那一套。文末的对比表和决策框架帮你做选择。

所有示例都使用专用代理网络、HTTP到HTTPS重定向和生产级默认配置。这些配置针对Virtua Cloud VPS上的Ubuntu 24.04。

反向代理对VPS上的Docker容器有什么作用?

反向代理位于互联网和Docker容器之间。它终止TLS(HTTPS),根据主机名将请求路由到正确的容器,并暴露一对端口(80/443)而不是每个服务一个端口。容器不需要处理证书,也不需要直接绑定公共端口。

没有反向代理,每个容器都需要自己的公共端口。访问者需要通过example.com:3000访问一个服务,example.com:8080访问另一个。反向代理让你可以使用标准端口上的app.example.comapi.example.com

以下三套配置假设:

每个示例都部署相同的后端:traefik/whoami镜像,返回HTTP头信息和容器信息。之后替换成你的真实应用即可。

如何将Traefik配置为Docker反向代理并启用自动HTTPS?

Traefik通过读取Docker标签自动发现容器。你在每个服务上以标签形式添加路由规则。容器启动后,Traefik检测到它,请求Let's Encrypt证书并开始路由流量。无需重新加载配置。

创建项目目录:

mkdir -p ~/traefik-proxy && cd ~/traefik-proxy

创建所有代理服务共享的Docker网络:

docker network create proxy

创建docker-compose.yml

services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--log.level=WARN"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./acme.json:/acme.json
    networks:
      - proxy
    security_opt:
      - no-new-privileges:true

  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`app.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
    networks:
      - proxy

networks:
  proxy:
    external: true

启动前,创建权限受限的证书存储文件:

touch acme.json && chmod 600 acme.json

如果acme.json权限过于开放,Traefik会拒绝启动。600确保只有所有者能读取其中存储的私钥。

启动:

docker compose up -d

确认两个容器都在运行:

docker compose ps

traefikwhoami都应显示Up状态。从本地机器(不是服务器)测试:

curl https://app.example.com

响应包含whoami输出和请求头。响应中的X-Forwarded-For头表明Traefik正在代理流量并终止TLS。

标签的作用:

  • traefik.enable=true启用此容器(因为设置了exposedbydefault=false
  • traefik.http.routers.whoami.rule=Host(...)按主机名匹配请求
  • traefik.http.routers.whoami.tls.certresolver=letsencrypt指示Traefik为此域名获取证书

要添加另一个服务,在同一proxy网络上的任意Compose文件中添加带有相应标签的服务。Traefik会自动检测到它。

在Traefik中挂载Docker socket安全吗?

挂载/var/run/docker.sock赋予Traefik对Docker API的完全访问权限。如果攻击者攻破了Traefik,就能创建容器、读取环境变量(包括密钥)并在主机上提权到root。:ro标志只在文件系统层面防止写入,不限制Docker API调用。

在生产环境中,使用Docker socket代理。它位于Traefik和Docker守护进程之间,过滤API调用,只允许对容器元数据的读取操作。

docker-compose.yml中添加:

services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy:0.4
    container_name: socket-proxy
    restart: unless-stopped
    environment:
      CONTAINERS: 1
      NETWORKS: 1
      SERVICES: 0
      TASKS: 0
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - socket-proxy
    security_opt:
      - no-new-privileges:true

  traefik:
    image: traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    depends_on:
      - socket-proxy
    command:
      - "--providers.docker.endpoint=tcp://socket-proxy:2375"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--log.level=WARN"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./acme.json:/acme.json
    networks:
      - proxy
      - socket-proxy
    security_opt:
      - no-new-privileges:true
networks:
  proxy:
    external: true
  socket-proxy:
    driver: bridge
    internal: true

Traefik不再直接挂载Docker socket。socket-proxy网络设置为internal: true,意味着没有出站互联网访问。socket代理只允许对containers和networks端点的GET请求。

如何将Caddy配置为Docker反向代理并启用自动HTTPS?

Caddy自动处理HTTPS,除了域名外不需要任何配置。将域名指向服务器,写入Caddyfile,Caddy就会从Let's Encrypt获取并续期证书。不需要resolver配置,不需要ACME设置。这是Docker反向代理获取HTTPS的最短路径。

创建项目目录:

mkdir -p ~/caddy-proxy && cd ~/caddy-proxy

创建共享代理网络(如果已为Traefik创建则跳过):

docker network create proxy

创建Caddyfile

app.example.com {
	reverse_proxy whoami:80
	encode gzip
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
	}
}

这就是全部代理配置。Caddy读取域名,请求证书,将流量代理到端口80上的whoami容器。不需要证书resolver,不需要ACME邮箱(Caddy使用机器默认值,或者你可以全局设置),不需要管理存储路径。

创建docker-compose.yml

services:
  caddy:
    image: caddy:2.11
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy

networks:
  proxy:
    external: true

volumes:
  caddy_data:
  caddy_config:

443:443/udp端口启用Caddy原生支持的HTTP/3(QUIC)。cap_drop: ALL配合cap_add: NET_BIND_SERVICE移除了所有Linux capability,只保留绑定1024以下端口所需的那一个。

启动:

docker compose up -d

检查容器状态:

docker compose ps

两个容器都应显示Up。从本地机器测试,使用详细输出:

curl -v https://app.example.com

在输出中寻找HTTP/2 200。你还应该看到Caddyfile中的安全头(Strict-Transport-SecurityX-Content-Type-Options等)。

要添加另一个服务,在Caddyfile中添加新的域名块和reverse_proxy指令,然后重新加载:

docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

不需要重启容器。Caddy不需要Docker socket,不会自动发现容器。你在Caddyfile中管理路由。

如何将Nginx配置为Docker反向代理并使用Let's Encrypt?

Nginx让你完全控制每个代理指令、头部、缓冲区大小和缓存规则。代价是手动配置。Nginx不会自己获取TLS证书。你需要搭配Certbot,由它处理ACME挑战和证书续期。

创建项目目录:

mkdir -p ~/nginx-proxy && cd ~/nginx-proxy

创建共享代理网络:

docker network create proxy

nginx/conf.d/app.conf中创建Nginx配置:

mkdir -p nginx/conf.d
server {
    listen 80;
    server_name app.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

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

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers off;

    server_tokens off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location / {
        proxy_pass http://whoami:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server_tokens off;隐藏响应头中的Nginx版本号。暴露版本信息会帮助攻击者针对已知漏洞进行攻击。

创建docker-compose.yml

services:
  nginx:
    image: nginx:1.28
    container_name: nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - certbot_webroot:/var/www/certbot:ro
      - certbot_certs:/etc/letsencrypt:ro
    networks:
      - proxy
    depends_on:
      - whoami

  certbot:
    image: certbot/certbot
    container_name: certbot
    restart: unless-stopped
    volumes:
      - certbot_webroot:/var/www/certbot
      - certbot_certs:/etc/letsencrypt
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy

networks:
  proxy:
    external: true

volumes:
  certbot_webroot:
  certbot_certs:

Nginx要求证书文件在启动前就存在。上面的配置引用了/etc/letsencrypt/live/app.example.com/fullchain.pem,但它还不存在。首次获取证书时,临时将app.conf替换为纯HTTP版本:

server {
    listen 80;
    server_name app.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

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

启动Nginx和后端:

docker compose up -d nginx whoami

请求初始证书:

docker compose run --rm certbot certonly \
  --webroot \
  --webroot-path=/var/www/certbot \
  -d app.example.com \
  --email you@example.com \
  --agree-tos \
  --no-eff-email

证书获取后,恢复完整的app.conf(上面展示的包含SSL服务器块的版本),然后启动完整stack:

docker compose up -d

确认所有容器都在运行:

docker compose ps

从本地机器测试:

curl -v https://app.example.com

响应头中的server:应显示nginx但不带版本号,这说明server_tokens off生效了。

要添加另一个服务,在nginx/conf.d/中创建新的.conf文件,然后重新加载:

docker compose exec nginx nginx -s reload

证书续期方面,Certbot容器每12小时运行certbot renew。续期后重新加载Nginx以使用新证书。可以用cron任务或检查证书修改时间的脚本来自动化这一过程。关于Nginx反向代理配置的深入内容,参见 Nginx反向代理配置教程

Traefik、Caddy和Nginx在Docker反向代理方面如何对比?

Traefik在自动发现方面胜出。Caddy在简洁性方面胜出。Nginx在控制力方面胜出。下表列出了在VPS上运行Docker容器时需要权衡的要点。

特性 Traefik v3 Caddy 2.11 Nginx 1.28
自动发现 是(Docker标签) 否(手动Caddyfile) 否(手动conf文件)
TLS自动化 内置ACME 内置ACME 需要Certbot sidecar
配置方式 Docker标签 + 静态YAML/CLI Caddyfile或JSON API nginx.conf文件
配置重载 容器事件时自动 caddy reload(零停机) nginx -s reload(零停机)
需要Docker socket 是(或socket代理)
HTTP/3(QUIC) 实验性 是(默认) 通过第三方模块
中间件/插件 内置(限流、认证、头部) 内置 + Go插件 通过配置指令
社区/文档 大型、活跃、文档好 较小、文档优秀 最大、文档全面
学习曲线 中等(标签 + 静态配置) 低(Caddyfile直观) 高(指令多)

哪个反向代理内存占用最少?

空闲内存占用在VPS上很重要,每个MB都很宝贵。这些数据来自Virtua Cloud 4 vCPU / 8 GB RAM VPS上运行Ubuntu 24.04时的docker stats --no-stream。每个代理在测量前无流量空闲运行。

代理 空闲RAM 镜像大小
Traefik v3.6 ~17 MB ~242 MB
Caddy 2.11 ~14 MB ~88 MB
Nginx 1.28 ~5 MB ~240 MB
Nginx + Certbot ~5 MB + ~25 MB ~240 MB + ~298 MB

Nginx内存占用远远最低。Caddy居中。Traefik较高的内存占用来自于在内存中维护Docker provider状态和路由表。三者都使用默认镜像(基于Debian/Alpine)。Alpine变体可以减小镜像大小,但可能带来某些扩展的兼容性问题。

轻负载下(通过wrk发送100个并发请求),三者在这个VPS规格上都能处理流量,CPU和内存没有明显增加。差异只在大规模场景或最小VPS套餐上才有意义。

如何为Docker配置选择合适的反向代理?

正确的选择取决于你运行多少服务、变更频率以及你已有的知识。

选择Traefik的场景:

  1. 运行大量频繁变更的容器(每周添加/移除服务)
  2. 想要零接触路由:部署带标签的容器即可上线
  3. 使用Docker Swarm或需要跨多节点的服务发现
  4. 接受Docker socket暴露(生产环境配合socket代理)

选择Caddy的场景:

  1. 运行少量很少变更的服务
  2. 想要最简单的自动HTTPS路径
  3. 不想挂载Docker socket
  4. 看重小镜像和低内存占用
  5. 想要无需额外配置的HTTP/3支持

选择Nginx的场景:

  1. 已经熟悉Nginx配置
  2. 需要对代理行为进行精细控制(缓冲区、缓存、按location自定义头部)
  3. 想要尽可能低的内存占用
  4. 团队已有Nginx工具链和监控
  5. 不介意单独管理Certbot

决策流程:

  1. 运行超过5个定期变更的Docker服务? -> Traefik
  2. 需要精细的代理调优或已经在用Nginx? -> Nginx
  3. 想要最少的组件和最快的搭建? -> Caddy

对于部署一两个个人项目的独立开发者来说,Caddy是最好的起点。对于管理大量容器的DevOps团队,Traefik的自动发现物有所值。对于已在其他地方使用Nginx的团队,继续用Nginx保持技术栈一致 VPS上的Docker网络:bridge、host和macvlan详解

三种代理的安全加固

无论选择哪个代理,都要应用这些基本安全实践。

安全头部。 以上三个示例都包含HSTS、X-Content-Type-Options、X-Frame-Options和Referrer-Policy。对于Traefik,通过middleware标签添加:

labels:
  - "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
  - "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
  - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
  - "traefik.http.middlewares.security-headers.headers.frameDeny=true"
  - "traefik.http.routers.whoami.middlewares=security-headers"

限流。 Traefik内置限流middleware。Caddy有可作为插件使用的rate_limit指令。Nginx在配置中使用limit_req_zone。限流保护后端免受暴力攻击和滥用。

Docker网络隔离。 每个示例都使用外部proxy网络。后端服务不应在默认bridge网络上。只有需要代理的容器才加入proxy网络。数据库容器和内部服务留在独立的内部网络中 Docker安全加固:VPS上的Rootless模式、Seccomp和AppArmor

防火墙。 只有80和443端口应该公开访问。Docker直接操作iptables,可能绕过UFW规则。解决方案见 修复Docker绕过UFW防火墙:4种经过测试的VPS解决方案

日志。 出问题时查看代理日志:

# Traefik
docker logs traefik -f

# Caddy
docker logs caddy -f

# Nginx
docker logs nginx -f

对于Traefik,临时设置--log.level=DEBUG来诊断路由或证书问题。对于Caddy,在Caddyfile中启用全局debug选项。对于Nginx,检查容器内/var/log/nginx/error.log中的error.log

遇到问题?

症状 可能原因 解决方法
证书未签发 DNS A记录未指向VPS IP dig app.example.com验证
Traefik所有路由返回404 容器不在proxy网络中 检查docker network inspect proxy
Caddy端口80 "permission denied" 缺少NET_BIND_SERVICE capability 添加cap_add: NET_BIND_SERVICE
Nginx证书 "no such file" Certbot尚未运行 先运行certbot certonly
ERR_CONNECTION_REFUSED 防火墙阻止80/443 检查ufw statusiptables -L
Traefik acme.json权限错误 文件权限过于开放 运行chmod 600 acme.json
代理在服务器上正常,外部访问失败 只在localhost测试 从本地机器用curl测试

关于反向代理之外的生产加固,参见 Docker Compose资源限制、健康检查与重启策略 了解Compose stack的资源限制和健康检查。


版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。

准备好亲自尝试了吗?

几秒内部署您自己的服务器。支持 Linux、Windows 或 FreeBSD。

查看 VPS 方案