如何用Nginx实现无端口访问多Docker部署的网站?
实现多域名无端口访问Docker部署网站的方案
问题背景
在DigitalOcean Droplet上用Docker部署了2个网站,目前只能通过Docker Compose指定的端口(如3001:443)访问。希望用户仅通过域名就能用HTTP/HTTPS直接访问,但Droplet上已运行的Nginx占用80端口,且443端口只能被一个站点占用,启动第二个站点会触发端口冲突。当前HTTP请求因宿主机Nginx占用80无法正常跳转,只能通过带端口的HTTPS访问网站。
当前配置信息
宿主机Nginx配置
server { listen 443; server_name example.com; location ~ ^/(api) { proxy_pass http://example-backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_cache_bypass $http_upgrade; } } server { listen 443; server_name demo.com; location ~ ^/(api) { proxy_pass http://demo-backend:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_cache_bypass $http_upgrade; } }
其中一个网站的Docker Compose配置
version: '3.7' services: example-backend: build: context: ./backend ports: - "8080:8080" restart: always environment: SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL} SPRING_DATASOURCE_USERNAME: ${MYSQL_DB_USER} SPRING_DATASOURCE_PASSWORD: ${MYSQL_DB_PASSWORD} SPRING_JPA_HIBERNATE_DDL-AUTO: update SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME} SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD} # 存放静态资源的NGINX容器(已完成构建) example-frontend: build: context: ./frontend ports: - "3001:443" restart: always depends_on: - example-backend volumes: - ./web/conf.d:/etc/nginx/conf.d - ./web/dhparam:/etc/nginx/dhparam - ./web/certbot/data:/usr/share/nginx/html/letsencrypt - ./web/certbot/conf/:/etc/nginx/ssl
该网站容器内的Nginx配置
server { listen 80; server_name example.com; location ~ /.well-known/acme-challenge/ { allow all; root /usr/share/nginx/html/letsencrypt; } location / { return 301 https://example.com:3001$request_uri; } } server { listen 443 ssl; server_name example.com; server_tokens off; ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem; ssl_dhparam /etc/nginx/dhparam/dhparam-2048.pem; location / { root /usr/share/nginx/html/; try_files $uri /index.html; } location /api/ { proxy_pass http://example-backend:8080; } }
解决方案
核心思路:将宿主机Nginx作为统一入口,反向代理到各个Docker容器的内部端口;容器不再暴露80/443到宿主机,仅在内部网络通信;统一由宿主机处理SSL证书和HTTP转HTTPS跳转。
步骤1:修改Docker Compose配置,取消宿主机端口映射
把容器的端口映射改为仅内部暴露,避免宿主机端口冲突,同时添加自定义网络提升通信隔离性:
version: '3.7' # 添加自定义网络 networks: example-network: driver: bridge services: example-backend: build: context: ./backend # 取消宿主机端口映射,仅内部暴露 expose: - "8080" restart: always environment: SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL} SPRING_DATASOURCE_USERNAME: ${MYSQL_DB_USER} SPRING_DATASOURCE_PASSWORD: ${MYSQL_DB_PASSWORD} SPRING_JPA_HIBERNATE_DDL-AUTO: update SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME} SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD} networks: - example-network example-frontend: build: context: ./frontend # 取消宿主机端口映射,仅内部暴露 expose: - "443" - "80" restart: always depends_on: - example-backend volumes: - ./web/conf.d:/etc/nginx/conf.d - ./web/dhparam:/etc/nginx/dhparam - ./web/certbot/data:/usr/share/nginx/html/letsencrypt - ./web/certbot/conf/:/etc/nginx/ssl networks: - example-network
另一个网站的Compose配置做同样修改,使用独立的自定义网络或共享网络均可。
步骤2:修改宿主机Nginx配置,添加完整反向代理规则
处理HTTP(80端口)跳转与证书验证
添加server块统一处理所有域名的HTTP请求:
server { listen 80; server_name example.com demo.com; # 处理Let's Encrypt证书验证请求,需将容器的certbot目录同步到宿主机(如/var/www/letsencrypt) location ~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; } # 所有HTTP请求跳转到HTTPS location / { return 301 https://$host$request_uri; } }
处理HTTPS(443端口)反向代理
修改原有443 server块,同时代理前端静态资源和后端API:
server { listen 443 ssl; server_name example.com; # 宿主机上的证书路径,建议统一在宿主机管理证书或同步容器证书目录 ssl_certificate /path/to/certs/example.com/fullchain.pem; ssl_certificate_key /path/to/certs/example.com/privkey.pem; ssl_dhparam /path/to/dhparam-2048.pem; # 代理前端静态资源到容器内部端口 location / { proxy_pass https://example-frontend:443; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 代理后端API请求 location ~ ^/(api) { proxy_pass http://example-backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_cache_bypass $http_upgrade; } } server { listen 443 ssl; server_name demo.com; ssl_certificate /path/to/certs/demo.com/fullchain.pem; ssl_certificate_key /path/to/certs/demo.com/privkey.pem; ssl_dhparam /path/to/dhparam-2048.pem; # 代理前端静态资源到demo容器 location / { proxy_pass https://demo-frontend:443; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 代理demo后端API请求 location ~ ^/(api) { proxy_pass http://demo-backend:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_cache_bypass $http_upgrade; } }
步骤3:简化容器内的Nginx配置
去掉端口跳转逻辑,由宿主机统一处理HTTP转HTTPS:
server { listen 80; server_name example.com; location ~ /.well-known/acme-challenge/ { allow all; root /usr/share/nginx/html/letsencrypt; } # 跳转至HTTPS,无需指定端口 location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name example.com; server_tokens off; ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem; ssl_dhparam /etc/nginx/dhparam/dhparam-2048.pem; location / { root /usr/share/nginx/html/; try_files $uri /index.html; } location /api/ { proxy_pass http://example-backend:8080; } }
更简化的方式是让容器内Nginx仅监听80端口,宿主机代理时使用http://example-frontend:80,将SSL处理完全交给宿主机,减少容器内配置复杂度。
步骤4:重启服务生效
- 重启Docker容器:
docker-compose down && docker-compose up -d
- 重启宿主机Nginx:
sudo systemctl restart nginx
注意事项
- 确保宿主机能通过Docker服务名(如
example-frontend)访问容器内部端口,自定义网络下默认支持服务名解析。 - 证书管理建议统一在宿主机用Certbot申请,再挂载到各个容器,避免重复申请。
- 检查Droplet防火墙规则,确保80、443端口对外开放。
内容的提问来源于stack exchange,提问作者ServletException
相关产品推荐
相关产品推荐

