Files
fips_setup/plans/gitea-fips-subpath-fix.md
2026-06-07 11:24:02 -04:00

13 KiB

Gitea over FIPS: /git/ Sub-Path Fix Plan

Problem statement

Accessing Gitea through the FIPS mesh fails with:

Failed to load asset files from http://npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips/assets/js/index.js?v=1.24.6. Please make sure the asset files can be accessed

The landing page on the same .fips host loads fine.

Root cause (confirmed on the server)

The FIPS network path is healthy — this is not an MTU/PMTUD black-hole. The failure is a Gitea sub-path reverse-proxy misconfiguration on the server (VM410 / laantungir.net).

Server facts gathered via SSH (ubuntu@laantungir.net):

  • One Gitea instance runs at 127.0.0.1:3000.

  • Gitea config is injected by environment variables in /etc/systemd/system/gitea.service (these override /var/lib/gitea/conf/app.ini):

    • GITEA__server__DOMAIN=git.laantungir.net
    • GITEA__server__ROOT_URL=https://git.laantungir.net/ ← root path
    • GITEA__server__HTTP_PORT=3000
  • The .fips host is served by /etc/nginx/conf.d/fips-ingress.conf, in the server { server_name ~^npub1.+\.fips$; ... } block.

  • That block proxies Gitea only under /git/, and it strips the prefix:

    location ^~ /git/ {
        rewrite ^/git/?(.*)$ /$1 break;          # <-- strips /git/
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host git.laantungir.net;
        ...
    }
    location / {
        root /var/www/html;                      # <-- static landing page
        try_files $uri $uri/ =404;
    }
    

Why assets 404

  1. Browser opens http://<npub>.fips/git/.
  2. nginx strips /git/ and proxies / to Gitea.
  3. Gitea renders HTML, but because ROOT_URL is root (.../), it emits asset links as root-relative /assets/js/index.js, not /git/assets/....
  4. Browser requests http://<npub>.fips/assets/js/index.js.
  5. That path does not match location ^~ /git/; it falls through to location / (static webroot /var/www/html), which has no assets directory → 404.
  6. Gitea's JS bootstrap sees the 404 and throws "Failed to load asset files".
flowchart TD
    A[Browser opens npub.fips/git/] --> B[nginx strips /git/ proxies / to Gitea 3000]
    B --> C[Gitea ROOT_URL is root so emits /assets/... root-relative]
    C --> D[Browser requests npub.fips/assets/js/index.js]
    D --> E[No /git/ match falls to location / static webroot]
    E --> F[404 Not Found]
    F --> G[Gitea JS error Failed to load asset files]
    style B fill:#cce5ff
    style E fill:#ffcccc
    style F fill:#ffcccc

Key asymmetry vs other proxied services

The sibling location ^~ /relay/ and location ^~ /blossom/ blocks also strip their prefix — and that is fine for them, because those backends do not generate self-referential absolute URLs from a configured base. Gitea does (assets, redirects, cookies are derived from ROOT_URL). Therefore Gitea's /git/ block must be configured differently: the prefix must be preserved and ROOT_URL must include /git/.

Roadmap context

The operator is consolidating subdomains into apex sub-paths:

  • relay.laantungir.netlaantungir.net/relay
  • git.laantungir.netlaantungir.net/git

So the destination for Gitea is the /git/ sub-path on the apex host, and equivalently <npub>.fips/git/ on the mesh. This makes the path-based /git/ mount the correct, permanent design — not a throwaway workaround. Fixing it now is a down-payment on the end state.

The fix (two paired changes)

For Gitea to live correctly at /git/ on any host, two things must be true together:

  1. Gitea knows its base path: ROOT_URL ends in /git/ → Gitea then emits assets as /git/assets/... (STATIC_URL_PREFIX follows ROOT_URL).
  2. nginx preserves the prefix: proxy_pass passes /git/ through (do not strip it), so the prefixed asset/redirect requests reach Gitea.
flowchart TD
    A[Browser opens host/git/] --> B[nginx location ^~ /git/ proxy_pass keeps /git/ prefix]
    B --> C[Gitea ROOT_URL ends in /git/]
    C --> D[Gitea emits /git/assets/...]
    D --> E[Browser requests host/git/assets/js/index.js]
    E --> F[Matches /git/ location proxies to Gitea]
    F --> G[200 OK assets load]
    style B fill:#cce5ff
    style F fill:#d4edda
    style G fill:#d4edda

Single-ROOT_URL consideration

Gitea supports only one ROOT_URL. The transition has two front doors that must both serve Gitea at the same sub-path /git/:

  • https://laantungir.net/git/ (clearnet — the future)
  • http://<npub>.fips/git/ (FIPS — interim and ongoing)

Because the path component is identical (/git/), one ROOT_URL ending in /git/ produces correct root-relative asset links (/git/assets/...) on both hosts. The only host-specific concern is absolute links/redirects (login flows, some redirects) and cookie domain, which Gitea derives from ROOT_URL host + forwarded headers.

Two ways to handle the host portion during transition:

  • Simple (recommended to start): ROOT_URL=https://laantungir.net/git/. Asset loading works on .fips because asset paths are root-relative. Some absolute redirects may point at laantungir.net; acceptable for a transition.
  • Mesh-pure (optional polish): add sub_filter on the .fips vhost to rewrite https://laantungir.net/githttp://<npub>.fips/git in responses, so .fips sessions never bounce to clearnet.

Implementation

All paths below are on the server ubuntu@laantungir.net (VM410). Make timestamped backups before editing (the server already follows this convention).

Step 1 — Set Gitea ROOT_URL to end in /git/

Edit /etc/systemd/system/gitea.service:

# BEFORE
Environment=GITEA__server__ROOT_URL=https://git.laantungir.net/

# AFTER
Environment=GITEA__server__ROOT_URL=https://laantungir.net/git/

Notes:

  • DOMAIN/SSH_DOMAIN can remain git.laantungir.net until the subdomain is removed; they do not affect asset paths. (At final cutover, set DOMAIN=laantungir.net.)
  • STATIC_URL_PREFIX is unset, so it defaults to follow ROOT_URL/git/assets/.... Do not set a conflicting STATIC_URL_PREFIX.

Apply:

sudo systemctl daemon-reload
sudo systemctl restart gitea

Step 2 — Fix the .fips nginx vhost /git/ block (preserve prefix)

Edit /etc/nginx/conf.d/fips-ingress.conf, inside server { server_name ~^npub1.+\.fips$; ... }:

# BEFORE
location ^~ /git/ {
    rewrite ^/git/?(.*)$ /$1 break;            # strips prefix  <-- REMOVE
    proxy_pass http://127.0.0.1:3000;          # no trailing /git/
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host git.laantungir.net;
    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 http;
    proxy_cache_bypass $http_upgrade;
}
location = /git { return 301 /git/; }
# AFTER
location ^~ /git/ {
    # Preserve the /git/ prefix toward Gitea (do NOT strip it).
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;                # forward the .fips 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 http;
    proxy_cache_bypass $http_upgrade;
    client_max_body_size 512m;                 # allow git pushes / large uploads
}
location = /git { return 301 /git/; }

Key changes:

  • Remove the rewrite ^/git/?(.*)$ /$1 break; line so the /git/ prefix is preserved.
  • With location ^~ /git/ + proxy_pass http://127.0.0.1:3000; (no URI on proxy_pass), nginx forwards the original URI including /git/. (Do not add a trailing path to proxy_pass here, or nginx will rewrite the matched location prefix.)
  • Consider Host $host so Gitea/forwarded headers reflect the .fips host. If any Gitea host allow-list rejects it, fall back to Host git.laantungir.net (assets still work either way since they are root-relative under /git/).
  • Add client_max_body_size for git over HTTP and uploads.

Step 3 (optional) — Keep .fips sessions on the mesh via sub_filter

If absolute links/redirects to https://laantungir.net/git are undesirable for .fips users, add inside the same /git/ location:

    proxy_set_header Accept-Encoding "";        # required for sub_filter to see bodies
    sub_filter_once off;
    sub_filter_types text/html application/javascript application/json;
    sub_filter "https://laantungir.net/git" "http://$host/git";

This is a transition convenience and can be removed at final cutover.

Step 4 — Validate and reload

sudo nginx -t
sudo systemctl reload nginx
# gitea already restarted in Step 1

Step 5 — Add the clearnet laantungir.net/git mount (target state)

This mirrors the .fips block on the apex laantungir.net server blocks (HTTP→HTTPS and the 443 block) in /etc/nginx/conf.d/default.conf. Add to the laantungir.net server block(s):

location ^~ /git/ {
    proxy_pass http://127.0.0.1:3000;          # preserve /git/ prefix
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;                # laantungir.net
    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;
    proxy_cache_bypass $http_upgrade;
    client_max_body_size 512m;
}
location = /git { return 301 /git/; }

With ROOT_URL=https://laantungir.net/git/, this serves Gitea cleanly at the apex sub-path with no rewriting. The existing static landing page remains at /.

Step 6 — Final subdomain removal (later, separate change)

When ready to delete git.laantungir.net:

  1. Confirm laantungir.net/git/ and <npub>.fips/git/ both fully work.
  2. Set GITEA__server__DOMAIN=laantungir.net (and SSH_DOMAIN per SSH plan).
  3. Remove the git.laantungir.net server blocks from nginx; reload.
  4. Remove any sub_filter transition shims.
  5. Repeat the analogous consolidation for relay./relay (note: relay/websocket backends can keep prefix-stripping since they don't emit self-referential base URLs — unlike Gitea).

Verification

Server-side (on laantungir.net, against local nginx)

# .fips host, /git/ landing
curl -sS -D - -o /dev/null -H "Host: npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips" \
  http://127.0.0.1/git/ | grep -Ei 'HTTP/|Set-Cookie|Location'
# expect: 200 OK and Set-Cookie: i_like_gitea (Gitea backend reached)

# .fips host, asset under /git/
curl -sS -o /dev/null -w '%{http_code}\n' -H "Host: npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips" \
  "http://127.0.0.1/git/assets/js/index.js?v=1.24.6"
# expect: 200

# clearnet apex /git/ (after Step 5)
curl -sS -o /dev/null -w '%{http_code}\n' -H "Host: laantungir.net" \
  "http://127.0.0.1/git/assets/js/index.js?v=1.24.6"
# expect: 200 (or 301->https depending on block)

Client-side (from FIPS client VM ai, over the mesh)

HOST='npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips'

# Gitea landing under /git/
curl -6 -sS -D - -o /dev/null "http://$HOST/git/" | grep -Ei 'HTTP/|Set-Cookie'
# expect: 200 OK, Set-Cookie i_like_gitea

# The previously-failing asset, now under /git/
curl -6 -sS -o /dev/null -w '%{http_code} %{size_download}\n' "http://$HOST/git/assets/js/index.js?v=1.24.6"
# expect: 200 and a non-trivial size

# Confirm root-relative asset path Gitea now emits
curl -6 -sS "http://$HOST/git/" | grep -Eo 'src="[^"]*"|href="[^"]*"' | grep '/git/assets' | head

Browser

  • Open http://<npub>.fips/git/ (note the trailing /git/).
  • Clear cache / hard-reload if a stale Gitea HTML referencing root /assets/... was cached.
  • The "Failed to load asset files" error should be gone.

Rollback

  • nginx: restore the timestamped backup of fips-ingress.conf (and default.conf), then nginx -t && systemctl reload nginx.
  • Gitea: restore the previous ROOT_URL line in gitea.service, then systemctl daemon-reload && systemctl restart gitea.

Notes / distinguishing from the MTU failure mode

  • This failure returns a fast HTTP 404 with a small body for assets — the network is fine.
  • An MTU/PMTUD black-hole instead presents as small responses succeeding while large transfers hang/stall or return 000 (connection stalls mid-body). If that signature ever appears, investigate TCP MSS clamping for the fips0 MTU (1280) on sys-fips, not this nginx/Gitea path.