Nginx反向代理与端口转发失效:无法公网访问Airflow服务
问题描述
我在访问本地托管的网站时遇到问题,希望让部署在Docker容器中、Nginx代理后的Apache Airflow服务通过公网访问,现有配置如下:
- 拥有NoIP主机名
stuff.ddns.net,已配置IP自动更新,确保始终指向路由器; - 路由器已将80、443端口转发至本地Linux机器的静态IP;
- Linux机器的Docker容器中部署了Airflow Web服务器,设置环境变量
AIRFLOW__WEBSERVER__BASE_URL: 'https://stuff.ddns.net/airflow'。
尝试访问stuff.ddns.net/airflow时,浏览器报错:
Safari can't open the page "stuff.ddns.net/airflow" because Safari can't connect to the server "stuff.ddns.net"
我的nginx.conf配置:
# top-level http config for websocket headers # If Upgrade is defined, Connection = upgrade # If Upgrade is empty, Connection = close events { worker_connections 1024; } http { map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream airflow { server localhost:8080; } server { listen [::]:80; server_name stuff.ddns.net; return 302 https://$host$request_uri; } server { listen [::]:443 ssl; server_name stuff.ddns.net; ssl_certificate /run/secrets/stuff_ddns_net_pem_chain; ssl_certificate_key /run/secrets/stuff_ddns_net_key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_dhparam /run/secrets/dhparam.pem; ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; location /airflow/ { proxy_pass http://airflow; proxy_set_header Host $http_host; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } }
补充docker-compose.yml(截取关键部分):
version: '3.7' x-airflow-common: &airflow-common image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.4.0} # build: . environment: &airflow-common-env AIRFLOW__CORE__EXECUTOR: CeleryExecutor AIRFLOW__CORE__SQL_ALCHEMY_CONN_CMD: 'cat /run/secrets/sql_alchemy_conn' AIRFLOW__CELERY__RESULT_BACKEND_CMD: 'cat /run/secrets/result_backend' AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0 AIRFLOW__CORE__FERNET_KEY: '' AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true' AIRFLOW__CORE__LOAD_EXAMPLES: 'false' AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth' AIRFLOW__WEBSERVER__BASE_URL: 'https://stuff.ddns.net/airflow' AIRFLOW__WEBSERVER__ENABLE_PROXY_FIX: 'True' _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-} volumes: - ./storage/airflow/dags:/opt/airflow/dags - ./storage/airflow/logs:/opt/airflow/logs - ./storage/airflow/plugins:/opt/airflow/plugins user: "${AIRFLOW_UID:-1000}:0" secrets: - sql_alchemy_conn - result_backend - machine_pass depends_on: &airflow-common-depends-on redis: condition: service_healthy postgres: condition: service_healthy x-stuff-common: &stuff-common restart: unless-stopped networks: - ${DOCKER_NETWORK:-stuff} services: nginx: <<: *stuff-common container_name: stuff-nginx image: nginxproxy/nginx-proxy:alpine hostname: nginx ports: - ${PORT_NGINX:-80}:80 volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ./config/nginx.conf:/etc/nginx/nginx.conf:ro secrets: - stuff_ddns_net_pem_chain - stuff_ddns_net_key - dhparam.pem airflow-webserver: <<: *stuff-common <<: *airflow-common container_name: stuff-airflow-webserver command: webserver ports: - ${PORT_UI_AIRFLOW:-8080}:8080 healthcheck: test: ["CMD", "curl", "--fail", "http://localhost:${PORT_UI_AIRFLOW:-8080}/airflow/health"] interval: 10s timeout: 10s retries: 5 depends_on: <<: *airflow-common-depends-on airflow-init: condition: service_completed_successfully networks: stuff: name: ${DOCKER_NETWORK:-stuff} secrets: ... <truncated>
解决思路
1. 排查基础网络连通性
- 验证DDNS解析:在公网环境(比如手机4G)执行
nslookup stuff.ddns.net,确认返回的IP是你的公网IP,且和路由器WAN口IP一致。如果解析错误,检查NoIP客户端的运行状态,确保IP更新任务正常执行。 - 测试端口可达性:用
telnet stuff.ddns.net 443或在线端口检测工具测试80、443端口是否开放。如果端口不可达,检查路由器端口转发规则:- 确认转发的目标IP是Linux机器的静态内网IP,不是动态分配的;
- 检查路由器是否开启了防火墙,是否拦截了80/443端口的入站请求;
- 部分ISP会封禁80/443端口,可以尝试换用其他端口(比如8080/8443)测试,同时调整路由器转发和Nginx监听配置。
2. 修正Docker内部网络访问问题
Nginx容器里的upstream airflow配置的是localhost:8080,但在Docker网络中,localhost指向Nginx容器自身,而非宿主机或Airflow容器。需要修改:
- 方案一:使用Docker服务名访问:因为Nginx和Airflow在同一个自定义网络
stuff中,直接用Airflow服务名airflow-webserver代替localhost:upstream airflow { server airflow-webserver:8080; } - 方案二:使用宿主机内网IP:如果不想依赖Docker服务发现,也可以配置成Linux机器的静态内网IP,比如
192.168.x.x:8080。
3. 调整Nginx代理配置细节
- 开放Nginx的443端口:当前
docker-compose.yml中Nginx只映射了80端口到宿主机,443端口未映射,需要添加:
否则路由器转发的443请求无法到达Nginx容器。ports: - ${PORT_NGINX:-80}:80 - ${PORT_NGINX_SSL:-443}:443 - 修正
proxy_pass路径:当前location /airflow/对应的proxy_pass http://airflow会把请求转发到http://airflow/,而Airflow的Base URL设置了/airflow前缀,需要确保路径匹配,修改为:location /airflow/ { proxy_pass http://airflow/airflow/; # 其他配置保持不变 } - 检查SSL配置有效性:进入Nginx容器执行
nginx -t,验证SSL证书、密钥和dhparam文件是否可读且格式正确。如果证书加载失败,Nginx无法启动443端口监听,会导致连接失败。
4. 验证Airflow服务状态
- 进入Airflow容器执行
curl http://localhost:8080/airflow/health,确认服务正常响应。如果返回失败,检查Airflow日志,确认Webserver是否正常启动。 - 确保
AIRFLOW__WEBSERVER__ENABLE_PROXY_FIX设置为True,这个配置让Airflow正确识别代理传递的请求头,生成正确的URL链接。
内容的提问来源于stack exchange,提问作者Fomalhaut -C
相关产品推荐
相关产品推荐

