Gitea通过Nginx Proxy Manager反代后,外网访问域名返回502 Bad Gateway,但NPM面板显示Online,直接访问宿主机IP+端口也无法打开。

排查过程

第一步:确认NPM能否访问到Gitea容器

在服务器上执行:

docker exec npm curl -v http://gitea:3000

如果返回HTTP/1.1 200 OK,说明NPM到Gitea的反代链路本身没问题,问题出在Gitea的配置上。

第二步:检查Gitea的ROOT_URL配置

docker exec gitea cat /data/gitea/conf/app.ini | grep ROOT_URL

如果返回的是:

ROOT_URL = http://gitea:3000/

或者:

ROOT_URL = http://x.x.x.x:3999/

说明Gitea的ROOT_URL配置不正确,导致页面资源链接错误,Cloudflare无法正常加载,返回502。

第三步:检查curl返回内容

注意curl输出里这一行:

appUrl: 'http://gitea:3000/'

这是Gitea实际运行时读到的ROOT_URL,如果不是你的域名,说明app.ini配置未生效或未正确修改。

根本原因

Gitea的app.ini中以下几项配置错误:

[server]
DOMAIN = x.x.x.x          # 填了IP,应该填域名
SSH_DOMAIN = x.x.x.x      # 填了IP,应该填域名
ROOT_URL = http://gitea:3000/  # 应该填完整的HTTPS域名

解决方案

方案一:直接修改app.ini(推荐)

nano /path/to/gitea/conf/app.ini

修改以下三项:

[server]
DOMAIN = your.domain.com
SSH_DOMAIN = your.domain.com
ROOT_URL = https://your.domain.com

方案二:在docker-compose.yml中通过环境变量覆盖

services:
  server:
    image: docker.gitea.com/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=xxxxx
      - GITEA__database__USER=xxxxx
      - GITEA__database__PASSWD=xxxxx
      - GITEA__server__DOMAIN=your.domain.com           # ← 加这行
      - GITEA__server__SSH_DOMAIN=your.domain.com       # ← 加这行
      - GITEA__server__ROOT_URL=https://your.domain.com # ← 加这行
    restart: always
    networks:
      - gitea
      - npm_default
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3999:3000"
      - "2223:22"
    depends_on:
      - db

  db:
    image: docker.io/library/postgres:14
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

networks:
  gitea:
    external: false
  npm_default:
    external: true

修改完成后重启:

cd /path/to/gitea
docker-compose up -d

验证是否生效

docker exec gitea cat /data/gitea/conf/app.ini | grep -E "DOMAIN|ROOT_URL"

返回结果应为:

DOMAIN = your.domain.com
SSH_DOMAIN = your.domain.com
ROOT_URL = https://your.domain.com

再次用curl验证appUrl:

docker exec npm curl -s http://gitea:3000 | grep appUrl

返回应为:

appUrl: 'https://your.domain.com/'

FAQ

Q:NPM面板显示Online但外网还是502,怎么判断是哪里的问题?

在服务器上执行docker exec npm curl -v http://容器名:端口,如果返回200说明NPM到容器链路正常,问题在应用配置层面;如果连接失败,说明网络或容器本身有问题。

Q:app.ini修改后重启,但配置好像没生效?

Gitea环境变量的优先级高于app.ini。如果compose里设置了错误的环境变量,会覆盖app.ini的配置。建议统一用环境变量管理,避免两处配置冲突。

Q:ROOT_URL填错了会有什么表现?

页面可能可以打开但样式全部丢失,或者重定向到错误地址,或者直接502。curl返回内容里的appUrl字段可以直接看到Gitea实际使用的ROOT_URL。

Q:DOMAIN和ROOT_URL有什么区别?

  • DOMAIN:Gitea对外显示的域名,影响SSH克隆地址等

  • ROOT_URL:完整的访问URL,包含协议和路径,影响所有页面跳转和资源加载

  • 两者都需要正确设置,缺一不可

Q:为什么用了反代还要配置ROOT_URL?

因为Gitea需要知道自己对外暴露的地址,才能正确生成页面内的链接、重定向地址、OAuth回调地址等。如果ROOT_URL还是内网地址或容器名,生成的链接就会指向错误地址,导致页面无法正常加载。