HTTPS proxy

Reading time 4 min

MarkStack has no built-in TLS server. A host/reverse proxy supplies HTTPS, certificates/renewal and reachability controls. Use separate reader/editor hosts where both exist. Serve only reader output publicly; keep the optional editor on its intended private perimeter and upstream loopback.

The nginx blocks below are templates for a host already equipped with nginx, actual certificates, DNS and an approved service process. They do not issue certificates, configure firewall, grant filesystem access or authenticate private readers. Validate final adapted configuration on staging before reload.

Boundary map

Layer Responsibility
Static reader Output bytes, route, index, media correctness
nginx reader Document root, MIME, 404, TLS, cache, reader gate
nginx editor HTTPS, intended audience, safe headers, upstream
Node editor Accounts, section grants, Host, origin, CSRF and limits
Operator Service, paths, certificate renewal, backups and recovery

Do not serve the project root. Accounts/settings/history/publication state belong outside output. Editor authentication protects writing, not a separately served reader. If reading is private, the gate must cover HTML, search JSON, fonts/scripts/images/downloads, not only home.

Static-reader template

server {
    listen 443 ssl;
    server_name docs.example.com;
    ssl_certificate /etc/ssl/example/docs-fullchain.pem;
    ssl_certificate_key /etc/ssl/example/docs-key.pem;

    root /srv/field-guide/app/dist;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
    error_page 404 /404.html;
}

try_files checks selected filesystem paths before returning 404. error_page internally serves the custom file while retaining error behavior unless you deliberately use a status override. Do not turn it into =200 or blanket home fallback. Verify unknown route’s actual status/content.

This root-site template needs your installed normal MIME configuration for CSS/JS/JSON/SVG/fonts and readable output/ancestors. A subpath site requires adapted path/root/alias handling and matching generated/authored URLs. Do not assume this root block is a complete subpath example.

Stock generated 404 has a literal / home link. Verify it, deep directory URLs, assets and search at actual hosted prefix. Review symlinks/permissions and directory-listing policy; unlike bundled preview’s containment tests, nginx’s final filesystem access follows its own settings.

Editor template

server {
    listen 443 ssl;
    server_name editor.example.com;
    ssl_certificate /etc/ssl/example/editor-fullchain.pem;
    ssl_certificate_key /etc/ssl/example/editor-key.pem;

    client_max_body_size 3m;
    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

This illustrates a single front proxy directly receiving the client. The forwarded address is replaced with connection address, not concatenated with an arbitrary client-supplied header. If another trusted proxy/CDN is in front, $remote_addr may be that proxy; review the entire trusted address/sanitization chain rather than changing to an unreviewed append recipe.

Editor setting Must match
Bind Local upstream 127.0.0.1 and selected port
Allowed hosts Actual forwarded browser hostname, port
Secure cookie Enabled for actual browser HTTPS
Trusted peers nginx’s actual connecting address
Body limit Node limit still applies independently

Exact values belong in service configuration. MARKSTACK_EDITOR_COOKIE_SECURE=1 selects Secure host-prefixed cookie mode. MARKSTACK_TRUSTED_PROXIES=loopback only suits a real local connecting peer. Pin trusts exact addresses/loopback, not CIDR ranges. It walks forwarded addresses from the right, skipping configured trusted peers; do not let an untrusted source choose client identity.

nginx defaults can change Host upstream; explicit proxy_set_header Host is important. $host suits this conventional 443 hostname example. If exposing a nonstandard external port, review/preserve the full expected hostname:port and add its exact value to allowed Hosts. X-Forwarded-Host is not a substitute for required Host.

The Node default JSON-body limit is 2 MiB; proxy 3m limit does not enlarge it. A tighter proxy cap may reject first. Align intended payload limits without disabling server safeguards. Do not change Node defaults based on one failed request before checking its actual content/size.

TLS, DNS and exposure

Requirement Check
Name DNS points to intended reachable host
Certificate Covers actual hostname, valid chain, key handling
Renewal Supported automated process and monitored failures
HTTP Intended redirect, challenge policy
Editor access Team network, access gate, no bypass upstream
Reader access Public decision or full separate private gate

Secure cookie flag does not create TLS. Do not sign in over public HTTP or disable Secure just to get a cookie stored. The upstream can be loopback HTTP while browser connection is HTTPS. HTTP-to-HTTPS and ACME challenge locations depend on your selected certificate process; see DNS setup.

Validate and reload

Read actual existing sites/service state before replacement. Preserve rollback config and check that paths/certificates/ports do not conflict. After adapting the template:

sudo nginx -t

Only after success and intended maintenance approval:

sudo systemctl reload nginx

Syntax validation is not a successful live TLS/auth test. Inspect reload/service logs, actual listener and public/private URLs. A command exit can coexist with wrong document root or blocked resources.

Staging acceptance

Check Required result
Reader Current home, deep, resources, search, diagram
Missing route 404 content, status, not soft 200 home
TLS, cookie Correct certificate and Secure browser cookie
Unauthenticated API Protected access rejected
Unknown Host, origin Server perimeter rejects request
Role, grant Synthetic outside-section writes denied
Publish Correct staged output, swap and actual served reader
Private files No accounts, runtime, server bytes accessible

Use synthetic accounts/content. Do not repeat completed mutations when logging fails; check original target. Restarting editor clears sessions/current job state; it does not stop separate reader serving.

nginx, runtime, DNS, certificates, firewall and production publication were not deployed by these documentation steps. Directive semantics were checked against official sources, not proven on your host. Caddy and Apache examples offers alternatives, not software to stack with this proxy blindly.

Official sources

Checked 12 October 2026: