Skip to main content

Reverse proxy and HTTPS

Jiandu defaults to loopback HTTP for one-machine evaluation. To reach it from another device, put one HTTPS reverse proxy in front of a private HTTP listener and configure Jiandu to trust that exact ingress boundary.

browser ── HTTPS ──> reverse proxy ── private HTTP ──> Jiandu :8077
│
└── the only published application port

Choose the proxy you already operate:

  • Caddy for the smallest configuration and automatic public certificates;
  • Nginx for an established Nginx deployment and explicit buffering/timeouts;
  • Traefik for label/file-driven container routing.

The repository ships a runnable Caddy Compose topology and reviewed Nginx and Traefik equivalents.

What Jiandu verifies​

In https_reverse_proxy mode, a request is accepted only when:

  1. the kernel-observed direct TCP peer is inside trusted_proxy_cidrs;
  2. the request has one Host header matching the host and port in public_origin; and
  3. browser mutations carry an exact matching Origin and acceptable Fetch Metadata.

This makes a forged Host, Origin, X-Forwarded-For, or identity header insufficient by itself. The proxy must still overwrite forwarding metadata and strip any trusted-identity headers so a browser cannot smuggle authority through the trusted hop.

1. Choose one canonical public origin​

Use the exact address people will enter, with no path or trailing slash:

https://documents.example.net

The origin must use a DNS hostname. Default HTTPS port 443 is omitted; a custom port remains part of the origin, for example https://documents.example.net:8443. The same origin must be used by passkeys, OIDC, and email connectors when those features are enabled. Changing it later can break registered OIDC callbacks and existing passkey relying-party expectations.

Arrange DNS and a trusted certificate before inviting users. A public CA is simplest for a public DNS name. A private CA works for a private name only when every browser/device and the relevant server-side runtimes trust that CA.

2. Identify the direct proxy address​

trusted_proxy_cidrs describes the address Jiandu sees on the accepted TCP connection—not the browser, LAN, CDN, Docker host, or an X-Forwarded-For value.

Prefer the smallest stable network:

  • 127.0.0.1/32 and/or ::1/128 when proxy and Jiandu share a host namespace;
  • one pinned container address such as 172.30.0.2/32; or
  • one pod/node CIDR only when the platform cannot give the proxy a stable address and that whole CIDR is an intentional trusted-proxy boundary.

Catch-all /0 networks are rejected. A broad private CIDR may validate but lets every peer in that network attempt the trusted hop, so use /32 or /128 whenever possible.

3. Configure Jiandu as one boundary​

/etc/jiandu/jiandu.json
{
"schema_version": 1,
"server": {
"bind": "0.0.0.0:8077",
"request_timeout_seconds": 30,
"ingress": {
"mode": "https_reverse_proxy",
"public_origin": "https://documents.example.net",
"trusted_proxy_cidrs": ["172.30.0.2/32"]
}
}
}

0.0.0.0:8077 is appropriate only inside an internal container/network namespace or behind a host firewall that limits the port to the proxy. When both processes share a host, prefer 127.0.0.1:8077 and trust loopback.

The equivalent environment boundary is:

export JIANDU_BIND=0.0.0.0:8077
export JIANDU_INGRESS_MODE=https_reverse_proxy
export JIANDU_PUBLIC_ORIGIN=https://documents.example.net
export JIANDU_INGRESS_TRUSTED_PROXY_CIDRS=172.30.0.2/32

Set all three JIANDU_INGRESS_* values together. Do not mix them with the deprecated public_hostname, trust_tls_termination, or trusted_proxy_cidrs settings. Partial and mixed-generation boundaries fail closed.

4. Preserve these proxy invariants​

Every proxy-specific example does the following:

  • listens on HTTPS for the exact public hostname;
  • sends the original Host (including a non-default port) to Jiandu;
  • overwrites X-Forwarded-Proto with https;
  • derives and overwrites X-Forwarded-For rather than trusting a browser-supplied value;
  • removes X-Jiandu-Proxy-Secret and the example identity assertion headers;
  • allows streaming/resumable request and response bodies without an unexpectedly short timeout; and
  • adds Strict-Transport-Security: max-age=31536000 at the TLS edge.

Jiandu also emits that HSTS policy in reverse-proxy mode. The examples deliberately omit includeSubDomains and preload; add either only if every affected subdomain has a permanent HTTPS commitment.

If you later configure trusted-proxy authentication, keep the strip rules at the public edge and add authoritative identity headers only after authentication. Rename the strip list to match every configured header.

5. Keep the backend private​

Only the proxy's HTTPS port should be published. In Docker Compose, use expose, not ports, on the Jiandu service and attach proxy plus Jiandu to an internal: true backend network. On a host, bind Jiandu to loopback. On Kubernetes, use a private ClusterIP service plus network policy and keep the application replica count at one.

Do not rely on Jiandu's 421 rejection as the only firewall. A direct client that can reach port 8077 is already inside more of the deployment boundary than necessary.

6. Validate through the real path​

Validate the configuration offline, then start both services:

jiandu --config /etc/jiandu/jiandu.json --check-config
curl --fail --silent https://documents.example.net/api/v1/health/live
curl --fail --silent https://documents.example.net/api/v1/health/ready

Run the following checks from a second device:

  1. the certificate is trusted and names the configured host;
  2. the exact public URL loads and sign-in works;
  3. a sample upload completes and its original downloads;
  4. the response includes Strict-Transport-Security: max-age=31536000;
  5. direct access to port 8077 is blocked by routing/firewall policy; and
  6. a request with the wrong host is rejected rather than routed to Jiandu.

For a local Caddy private-CA test, install the generated CA only on test devices rather than bypassing certificate warnings.

Diagnose by status​

SymptomLikely boundary error
421 ingress_not_allowedDirect proxy address is outside the CIDR, or forwarded Host/port differs from public_origin
403 origin_forbidden on mutationsBrowser origin differs, proxy rewrote authority, or a cross-site request reached Jiandu
Redirect/cookie loopPublic origin, proxy host, and browser URL are not exactly aligned
Every request is one rate-limit peerProxy did not supply a trustworthy overwritten X-Forwarded-For chain
Uploads fail near a fixed sizeProxy body limit or request buffering, not Jiandu's whole-document limit
Long download/upload resetsProxy read/send/idle timeout is shorter than the operation
OIDC callback failsProvider redirect URI or issuer does not match the configured public origin/provider ID

Continue with Caddy, Nginx, or Traefik.