Publish Nginx through Cloudflare DNS

This design publishes one deliberately public web service without asking Starlink to accept an inbound connection. The path is Starlink default IPv4 CGNAT → Netgate 2100 → isolated LAN4 DMZ (192.168.60.0/24) → Raspberry Pi 5 (192.168.60.10). Nginx listens only on 127.0.0.1:8080; a local cloudflared connector makes outbound tunnel connections to Cloudflare. There is no WAN port forward.

Publishing is not private administration. A public hostname is reachable by the public unless Cloudflare Access is placed in front of it. Keep Pi administration on Tailscale, and never expose SSH, the Netgate WebGUI, or a tunnel token through a web route.

Confirm the boundary before changing DNS

  • Give the Pi a DHCP reservation or static address of 192.168.60.10 in the DMZ. Check that its default gateway is the Netgate 2100 DMZ address.
  • Bind Nginx to loopback and verify with ss -lntp that nothing is listening on the Pi's DMZ address for the origin port.
  • Install cloudflared from Cloudflare's current official package instructions and run it as a supervised, unprivileged service where possible.
  • Export a Netgate configuration backup, record the intended change, and confirm there is no WAN NAT or pass rule for TCP 80, 443, 22, or the Nginx origin port.
  • Patch the Pi, Nginx, the application, and the connector before publication. Remove default sites and sample credentials.

Make the Nginx origin narrow

The origin needs to accept requests from the local connector, not from the internet. A minimal server block can look like this (replace the names and document root with the real application):

server {
    listen 127.0.0.1:8080;
    server_name www.example.net;
    root /srv/www/example.net/public;

    location / {
        try_files $uri $uri/ =404;
    }
}

Validate with nginx -t, reload through the service manager, and test locally with the correct Host header. Keep secrets, configuration files, sockets, backups, and private keys outside the document root. Add application authentication, safe headers, request-size limits, and rate controls appropriate to the application; a tunnel does not repair an unsafe application.

Set the application’s canonical HTTPS URL and proxy behavior deliberately. Only trust forwarding headers from the connector path or from a documented proxy; never treat a client-supplied identity header as proof of Cloudflare identity. Preserve Nginx access and error logs with UTC timestamps and enough request context to investigate abuse without recording passwords or tokens.

Create a named tunnel and its DNS route

  1. Use a Cloudflare account with multifactor authentication and add the authoritative DNS zone. Confirm that the domain and the intended hostname are yours.
  2. Create a named tunnel in the Cloudflare dashboard or with the current cloudflared tunnel workflow. Prefer a scoped tunnel token or credentials file with restrictive permissions; treat either as a password.
  3. On the Pi, configure one ingress rule for the exact hostname and the local origin, followed by a catch-all rule that returns an error. Do not route arbitrary hostnames to Nginx.
  4. Create the DNS route using Cloudflare’s tunnel workflow. The resulting record is normally a Cloudflare-managed CNAME to the tunnel hostname, not a record pointing at the Starlink or Netgate address.
  5. Run the connector under supervision, enable start-on-boot, and check that its user cannot modify unrelated system files. Do not put its token in shell history, source control, or the public web root.
tunnel: <named-tunnel-id>
credentials-file: /etc/cloudflared/<named-tunnel-id>.json

ingress:
  - hostname: www.example.net
    service: http://127.0.0.1:8080
  - service: http_status:404

The syntax and service installation options change, so use the current Cloudflare documentation for the exact platform command rather than copying a token or an old package repository into a deployment script.

Review DNS, TLS, and IPv6 together

At an external resolver, check the A and AAAA answers for the hostname, the CNAME chain, TTL, and whether the record is proxied as intended. Do not leave an old A record pointing to a residential address, and do not publish an AAAA record to a Pi or Netgate address unless you have intentionally secured and tested an IPv6 path. A stale AAAA can bypass an apparently correct IPv4-only design. Also review wildcard records and less obvious hostnames in the same zone.

Cloudflare terminates the public HTTPS connection when proxying the hostname. Choose the edge certificate mode according to the origin’s actual TLS configuration; if the origin is plain HTTP on loopback, the tunnel-to-origin hop is still local, but this does not make arbitrary DMZ traffic trustworthy. If using HTTPS to the origin, validate the origin certificate and hostname rather than disabling verification to hide a configuration error.

Keep public and private controls distinct

NeedControlWhat it does not provide
Public websiteCloudflare proxied hostname and the application’s own authorizationIt does not make an unpatched application safe.
Private browser applicationCloudflare Access application with an explicit identity and MFA policyIt does not grant SSH or network access to the Pi.
Pi and network administrationTailscale policy, host firewall, SSH keys, and local recoveryIt does not make the website public.

For a non-public dashboard, create the Access application before sharing the hostname, allow named users or groups, require MFA, and test both an allowed and a denied identity. Access is an edge authorization layer; the origin must still enforce its own authorization and must not trust an arbitrary CF-Access-Authenticated-User-Email or similar header. Keep a local recovery path for an Access, DNS, or internet outage.

Verify the no-forward design

  1. From the Pi, confirm Nginx answers on 127.0.0.1:8080 and the connector reports healthy outbound sessions.
  2. From a phone on cellular data, resolve the hostname and request HTTPS. Check the certificate name, status, redirect behavior, and expected application response.
  3. Check that direct requests to the Starlink-observed address, the Netgate WAN address, 192.168.60.10, TCP 8080, and TCP 22 do not reach the origin.
  4. Test an unrecognized hostname and a protected hostname; both must fail or require their intended policy rather than selecting the default Nginx site.
  5. Review Cloudflare tunnel events, Nginx logs, Netgate egress logs, and the Pi service journal. Confirm no inbound WAN rule was created by an installer.

Sequence navigation

Previous: Install Cloudflared and Create the Tunnel · Next: Use Tailscale for Private Raspberry Pi Administration

References