Gitea 28 muestra You cannot import from disallowed hosts porque el origen de la migración o del mirror resuelve a una IP privada y [migrations] ALLOWED_HOST_LIST no la incluye; se arregla añadiendo esa IP o su subred con el puerto, por ejemplo 172.31.105.0/24:3000, no el nombre del host. El mensaje cambió en la 28: en la 1.27 pedía revisar ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS. Lo reproduje el 18 de octubre de 2026 con dos contenedores Gitea 28.1.0 en una red Docker privada, en Linux arm64, y comparé con la 1.27.3. Tienes la versión en inglés de esta guía.

Puntos clave

  • El aviso de la interfaz web de la 28.x nombra ALLOWED_HOST_LIST/BLOCKED_HOST_LIST; la API devuelve un 422 con You can not import from disallowed hosts. en las dos versiones.
  • Por defecto (EGRESS_MODE = lax), Gitea 28 permite migrar desde hosts públicos y bloquea las redes privadas, loopback y CGNAT.
  • Un nombre de host en ALLOWED_HOST_LIST no desbloquea un destino privado: las migraciones exigen una entrada de IP, de CIDR o el alias private.
  • De 13 configuraciones probadas en la 28.1.0, la más estrecha que funcionó fue la subred Docker fijada con su puerto: 172.31.105.0/24:3000.
  • Una 1.27 con ALLOWED_DOMAINS = * y ALLOW_LOCALNETWORKS = true permitía todo; en la 28.1.0 esa misma configuración bloquea GitHub, porque * se descarta y el modo pasa a strict.

El error exacto: You cannot import from disallowed hosts

El texto depende de la versión y de por dónde migres. Desde la interfaz web, en el formulario Migrate From Git, la 28.1.0 muestra este aviso al intentar migrar http://gitea-source:3000/labadmin/demo.git, otro Gitea de la misma red Docker:

You cannot import from disallowed hosts. Please ask the admin to
check the ALLOWED_HOST_LIST/BLOCKED_HOST_LIST settings in the
[migrations] section.

Con la misma prueba, la 1.27.3 mostraba el texto antiguo, que es el que citan los hilos de foros anteriores a la 28:

You cannot import from disallowed hosts. Please ask the admin to
check ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings.

La API (POST /api/v1/repos/migrate) responde en las dos versiones con HTTP 422 y {"message":"You can not import from disallowed hosts."}. El registro del servidor no explica el motivo: solo anota la petición con su 422. Si el bloqueo afecta a un mirror que ya existía, el aviso aparece en el registro en cada sincronización:

.../mirror/mirror_pull.go:118:runSync() [E] SyncMirrors
[repo: <Repository 4:labadmin/mirror1>]: remote URL is not allowed:
migration/cloning from 'gitea-source' is not allowed.

¿Por qué aparece en Gitea 28 aunque antes funcionara?

Gitea 28.0.0, publicada el 30 de septiembre de 2026, pasa las migraciones y los mirrors por un proxy interno que aplica la política de salida en el momento de conectar (PR 39426). El anuncio lo resume así: "Migrations, mirrors, and other Git network operations now go through an internal proxy that applies the egress settings to direct connections." Las claves ALLOWED_DOMAINS, BLOCKED_DOMAINS y ALLOW_LOCALNETWORKS quedan obsoletas en favor de ALLOWED_HOST_LIST y BLOCKED_HOST_LIST, y una clave nueva, EGRESS_MODE, decide si la lista es exclusiva.

El cambio que más sorprende es que el nombre del host ya no autoriza una IP privada. El app.example.ini de la 28.1.0 lo dice en una línea: "Private and loopback addresses need a builtin or CIDR entry."

El motivo es de seguridad. El mismo anuncio corrige un fallo por el que un host que coincidía con ALLOWED_DOMAINS se saltaba la comprobación de IP. Eso permitía SSRF (falsificación de peticiones del lado del servidor) mediante DNS rebinding.

Si vienes de la 1.27, la guía para actualizar Gitea a la 28 con Docker cuenta el resto de cambios de salida, incluido el BLOCKED_HOST_LIST con comodines que impide arrancar.

Cómo lo he reproducido con dos Gitea en una red Docker

El laboratorio son dos contenedores docker.gitea.com/gitea:28.1.0 con SQLite en una red bridge con subred fija. gitea-source aloja un repositorio demo y target intenta migrarlo o reflejarlo. Si aún no tienes Gitea en marcha, la guía para instalar Gitea con Docker parte de un Compose parecido. Esta es la parte que importa:

services:
  source:
    image: docker.gitea.com/gitea:28.1.0
    networks:
      lab:
        ipv4_address: 172.31.105.10
        aliases: [gitea-source]
  target:
    image: docker.gitea.com/gitea:28.1.0
    env_file: [target.env]
    networks:
      lab:
        ipv4_address: 172.31.105.20
networks:
  lab:
    ipam:
      config: [{subnet: 172.31.105.0/24}]

target.env lleva cada configuración de [migrations] que probé, como variables GITEA__migrations__*. Entre prueba y prueba borré el volumen de target, porque el script de arranque de la imagen escribe las variables en app.ini y nunca borra las anteriores. Después lancé la migración por la API, con el nombre y con la IP del origen:

curl -u labadmin:tu_contrasena -H 'Content-Type: application/json' \
  -X POST http://127.0.0.1:30502/api/v1/repos/migrate \
  -d '{"clone_addr":"http://gitea-source:3000/labadmin/demo.git",
       "repo_name":"demo","service":"git"}'

Un 201 con el nombre del repositorio significa que la migración funcionó; un 422 con el mensaje anterior, que la política la bloqueó.

Configuración antigua y nueva de [migrations], probadas

La tabla resume las 13 configuraciones de la 28.1.0 y las de referencia en la 1.27.3. "LAN" es el Gitea de origen en 172.31.105.10:3000 y "GitHub" es https://github.com/octocat/Hello-World.git:

Versión Configuración de [migrations] LAN GitHub
1.27.3 sin claves Bloqueado No probado
1.27.3 ALLOW_LOCALNETWORKS = true Permitido No probado
1.27.3 ALLOWED_DOMAINS = github.com,gitea-source Bloqueado No probado
1.27.3 ALLOWED_DOMAINS = * y ALLOW_LOCALNETWORKS = true Permitido Permitido
28.1.0 sin claves Bloqueado No probado
28.1.0 ALLOW_LOCALNETWORKS = true (obsoleta) Permitido No probado
28.1.0 ALLOWED_DOMAINS = github.com,gitea-source (obsoleta) Bloqueado No probado
28.1.0 ALLOWED_DOMAINS = * y ALLOW_LOCALNETWORKS = true Permitido Bloqueado
28.1.0 ALLOWED_HOST_LIST = gitea-source Bloqueado No probado
28.1.0 ALLOWED_HOST_LIST = * Bloqueado No probado
28.1.0 ALLOWED_HOST_LIST = 172.31.105.* Bloqueado No probado
28.1.0 ALLOWED_HOST_LIST = private Permitido No probado
28.1.0 ALLOWED_HOST_LIST = 172.31.105.0/24 Permitido No probado
28.1.0 ALLOWED_HOST_LIST = 172.31.105.10:3000 Permitido Permitido
28.1.0 ALLOWED_HOST_LIST = 172.31.105.0/24:3000 Permitido Permitido
28.1.0 strict, ALLOWED_HOST_LIST = github.com,private Bloqueado No probado
28.1.0 strict, ALLOWED_HOST_LIST = github.com,172.31.105.10:3000 Permitido No probado

Las claves antiguas siguen funcionando en la 28.1.0 como alias, pero cada arranque deja en el registro un [E] Deprecation: config option [migrations].ALLOW_LOCALNETWORKS present, please use [migrations].ALLOWED_HOST_LIST instead. Las entradas * y 172.31.105.* no detienen el arranque; Gitea las ignora con un [E] (catch-all host pattern "*" matches every host and is not allowed y target "172.31.105.*" is not an IP, CIDR, named range, or valid hostname pattern) y la lista queda vacía.

La dificultad de ALLOWED_DOMAINS = * al actualizar

La fila más traicionera es la de ALLOWED_DOMAINS = * con ALLOW_LOCALNETWORKS = true, la forma habitual de "permitir todo" en la 1.27. En la 28.1.0 la traducción automática convierte * en *:*, la descarta por no válida y, como había una lista de dominios, activa el modo strict. El resultado es el contrario del que tenías: la red interna sigue permitida por el alias, y GitHub, Codeberg o cualquier host público devuelven You can not import from disallowed hosts.

La solución es borrar las dos claves antiguas de app.ini y escribir la configuración nueva. Si de verdad quieres permitir cualquier host público y tu red interna, deja EGRESS_MODE en lax y lista solo las redes privadas que necesites, como en la sección siguiente.

La ALLOWLIST mínima y segura que arregla el error

La entrada más estrecha que funcionó es la subred del origen con su puerto. En modo lax, una entrada sin puerto permite todos los puertos de esa IP, y private abre 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7 y 100.64.0.0/10 en todos los puertos. Eso permite a cualquier usuario que pueda migrar sondear servicios internos, que es justo el SSRF que la 28 intenta cerrar. Con la red Docker fijada, añade esto al servicio de Gitea que importa:

    environment:
      GITEA__migrations__EGRESS_MODE: "lax"
      GITEA__migrations__ALLOWED_HOST_LIST: "172.31.105.0/24:3000"

lax mantiene permitidos los hosts públicos y la lista solo se consulta para destinos privados. En mi prueba, con esta línea la migración desde gitea-source y desde GitHub devolvieron 201, y la misma IP en el puerto 22 siguió bloqueada. Si el origen es un servidor de tu LAN con IP fija, usa esa IP y su puerto, por ejemplo 192.168.1.50:443.

Fija la subred en el Compose (ipam en la red). Sin ella, Docker asigna otra subred cuando recreas la red y la entrada deja de coincidir. Si necesitas más de una entrada, sepáralas con comas: 172.31.105.0/24:3000, 192.168.1.50:443.

Si prefieres el modo strict

En strict la lista es exclusiva: cualquier host que no figure en ella se bloquea, también los públicos. Además, una entrada sin puerto solo permite el 80 y el 443, por eso github.com,private bloqueó el Gitea de origen en el puerto 3000. Con github.com,172.31.105.10:3000 la migración desde la LAN funcionó. Escribe en la lista cada forja pública de la que importes.

Cómo comprobar que el arreglo funciona

Reinicia el contenedor, revisa el registro de arranque y repite la migración. Estos dos comandos confirman la configuración efectiva y que no queda ninguna clave antigua en app.ini:

docker exec gitea grep -A4 '^\[migrations\]' /data/gitea/conf/app.ini
docker logs gitea 2>&1 | grep -E 'migrations|Deprecation'

En el bloque [migrations] del primero solo deben aparecer EGRESS_MODE y ALLOWED_HOST_LIST; el segundo no debe imprimir ningún ignores an invalid entry ni ningún Deprecation. Para un mirror existente, pulsa Synchronize Now en sus ajustes o llama a POST /api/v1/repos/{owner}/{repo}/mirror-sync. En el laboratorio, tras restaurar la entrada, el mirror trajo el commit nuevo del origen en la primera sincronización.

Alternativas si no puedes tocar la lista

Hay dos caminos cuando el administrador no quiere abrir rangos privados. El primero es publicar el Gitea de origen con su nombre público, detrás de su proxy inverso y con HTTPS. Si desde el servidor de destino ese nombre resuelve a una IP pública, el modo lax lo permite sin entradas.

El segundo es no migrar por la red: ejecuta git clone --mirror en tu máquina y git push --mirror hacia un repositorio vacío del destino. Ese envío no pasa por la política de salida, aunque pierdes las incidencias y las solicitudes de cambios.

Si estás pensando en cambiar de forja, Forgejo con Docker tiene su propia configuración de migraciones; no la he probado con esta versión.

Preguntas frecuentes

¿Basta con poner el nombre del contenedor en ALLOWED_HOST_LIST?

No. En la 28.1.0, ALLOWED_HOST_LIST = gitea-source siguió devolviendo el error, porque gitea-source resuelve a 172.31.105.10, una IP privada. Las migraciones exigen que un destino privado coincida con una entrada de IP, de CIDR o con el alias private.

¿Tengo que reiniciar Gitea después de cambiar la lista?

Sí. Gitea lee [migrations] al arrancar. Con variables GITEA__ en Docker, recrea el contenedor con docker compose up -d para que el script de arranque las escriba en app.ini.

¿Cambia algo la 28.1.0 frente a la 28.0.0?

La 28.1.0 salió el 6 de octubre de 2026 y pasa el enlace local, los rangos privados y CGNAT a la categoría de "restringidos". Siguen bloqueados por defecto, pero un CIDR explícito en la lista los permite (PR 39560). El texto del error es el mismo en las dos, según los ficheros de traducción de cada etiqueta.

Conclusión

El aviso You cannot import from disallowed hosts en Gitea 28 apareció en mis pruebas por dos causas: un origen en una IP privada sin entrada de IP en [migrations] ALLOWED_HOST_LIST, o una configuración antigua que la 28 traduce a modo strict. Borra las claves antiguas, deja EGRESS_MODE = lax y añade la IP o la subred del origen con su puerto en lugar de private. Si venías de ALLOWED_DOMAINS = *, revisa también que GitHub siga permitido después de actualizar.

Fuentes: [1] Blog de Gitea, anuncio de Gitea 28.0.0[1], [2] Gitea, PR 39426 sobre el proxy interno de Git[2], [3] Gitea, PR 39560 sobre rangos restringidos[3], [4] Gitea, app.example.ini de la versión 28.1.0[4], [5] Gitea, textos en inglés de la versión 1.27.3[5], [6] Gitea, textos en inglés de la versión 28.1.0[6], [7] Documentación de Gitea, referencia de configuración[7], [8] Documentación de Docker, ipv4_address en Compose[8].

Fuentes

  1. Blog de Gitea, anuncio de Gitea 28.0.0
  2. Gitea, PR 39426 sobre el proxy interno de Git
  3. Gitea, PR 39560 sobre rangos restringidos
  4. Gitea, app.example.ini de la versión 28.1.0
  5. Gitea, textos en inglés de la versión 1.27.3
  6. Gitea, textos en inglés de la versión 28.1.0
  7. Documentación de Gitea, referencia de configuración
  8. Documentación de Docker, ipv4_address en Compose