Skip to content

feat: add internalUrl option to avoid hairpin-NAT on self-fetches#32

Draft
juliusknorr wants to merge 1 commit into
mainfrom
feat/internal-url-rewrite
Draft

feat: add internalUrl option to avoid hairpin-NAT on self-fetches#32
juliusknorr wants to merge 1 commit into
mainfrom
feat/internal-url-rewrite

Conversation

@juliusknorr

Copy link
Copy Markdown
Member

Problem

When DocService and its nginx reverse proxy are co-located on the same host, internally-generated signed file URLs use the public hostname (e.g. https://eo.nextcloud.com/cache/...). When DocService or FileConverter fetches one of these URLs itself (via the /downloadfile proxy endpoint or a converter source download), the request must traverse the router. Many routers do not apply inbound NAT rules to traffic originating from the inside, so the connection fails or the TLS handshake breaks -- the classic hairpin-NAT / split-horizon DNS problem.

Solution

Adds services.CoAuthoring.server.internalUrl config key (default: "").

When both services.CoAuthoring.server.internalUrl and storage.externalHost are set, any URL passed to downloadUrlPromise whose origin matches storage.externalHost has its origin rewritten to internalUrl before the HTTP request is made. All URLs sent to browsers or included in Nextcloud callbacks are unaffected -- only the internal fetch path is rewritten.

Configuration

Add to your environment config (e.g. production-linux.json):

{
  "storage": {
    "externalHost": "https://eo.nextcloud.com"
  },
  "services": {
    "CoAuthoring": {
      "server": {
        "internalUrl": "http://localhost:8080"
      }
    }
  }
}

storage.externalHost is the public base URL already used to build signed storage URLs. internalUrl is the address DocService can reach itself on directly -- typically http://localhost:<port> where port matches services.CoAuthoring.server.port.

With both set, a fetch of https://eo.nextcloud.com/cache/files/data/abc/output.docx?md5=... becomes http://localhost:8080/cache/files/data/abc/output.docx?md5=..., staying entirely on-host with no TLS or NAT involved.

The rewrite is per-tenant aware and does not affect the rejectUnauthorized setting or any other request options.

When services.CoAuthoring.server.internalUrl is set alongside
storage.externalHost, any URL passed to downloadUrlPromise whose
origin matches externalHost is rewritten to use internalUrl instead.
This avoids hairpin-NAT / split-horizon DNS failures when DocService
fetches its own signed cache URLs (e.g. /downloadfile proxy, converter
source downloads).

Signed-off-by: Julius Knorr <jus@bitgrid.net>
Assisted-by: ClaudeCode:claude-sonnet-4-6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant