rommapp_docs/docs/install/reverse-proxy.md
Claude f65b062cce
docs: draft Wave 1 anchor pages (landing + quick-start + install/admin/dev)
First batch of real Wave 1 content for the v5.0 overhaul. These pages
establish the tone and cross-linking pattern for the rest of the wave.

- index.md: routing landing with intent-based cards
- getting-started/quick-start.md: 15-min docker-compose happy path,
  reusing the existing compose snippet and asciinema cast
- install/docker-compose.md: canonical production reference with
  volume table, env var table, and hardening checklist
- install/image-variants.md: slim vs full matrix + what's actually
  different between them
- install/reverse-proxy.md: Caddy / nginx / Traefik / NPM recipes
  with ROMM_BASE_URL guidance
- administration/users-and-roles.md: full 19-scope matrix across
  Viewer/Editor/Admin, invite flow, OIDC hooks
- developers/api-authentication.md: session / Basic / OAuth2 /
  Client API Token / OIDC flows with error semantics

Builds clean with --strict.
2026-04-18 16:15:48 +00:00

5.2 KiB

title description
Reverse Proxy Put RomM behind Caddy, nginx, Traefik, or Nginx Proxy Manager with TLS.

Reverse Proxy

The RomM container listens on plain HTTP on port 8080. For anything beyond localhost you should put it behind a reverse proxy that terminates TLS and forwards to the container.

!!! tip "WebSockets are required" RomM uses Socket.IO (both the general /ws/socket.io endpoint and the /netplay/socket.io endpoint) for live updates, scan progress, and Netplay. Every reverse-proxy recipe below keeps WebSocket support on — don't strip it out.

The examples here assume your RomM container is reachable at romm:8080 (by container name on a Docker network) or 192.168.1.100:8080 (by IP on the LAN). Swap to whatever's right for your setup.

Caddy

Dead-simple, auto-HTTPS via Let's Encrypt.

romm.mysite.com {
  encode zstd gzip

  header {
    Strict-Transport-Security "max-age=31536000;"
    X-XSS-Protection "1; mode=block"
    X-Frame-Options "SAMEORIGIN"
    X-Robots-Tag "noindex, nofollow"
    -Server
    -X-Powered-By
  }

  reverse_proxy romm:8080
}

If you just want HTTP on the LAN:

http://romm.mysite.com {
  reverse_proxy romm:8080
}

Nginx

HTTP only

server {
  listen 80 default_server;
  server_name romm.mysite.com;
  client_max_body_size 0;

  location / {
    proxy_pass http://romm:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $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 $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

HTTPS with HSTS

server {
  listen 80 default_server;
  server_name _;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name romm.mysite.com;
  ssl_certificate     /etc/ssl/romm/fullchain.pem;
  ssl_certificate_key /etc/ssl/romm/privkey.pem;
  client_max_body_size 0;

  location / {
    proxy_pass http://romm:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $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 $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    server_tokens off;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
  }
}

!!! note "client_max_body_size 0" Required so large ROM uploads aren't rejected by nginx before they reach RomM.

Traefik

Dynamic configuration file

http:
  routers:
    romm:
      entryPoints:
        - websecure
      rule: "Host(`romm.mysite.com`)"
      middlewares:
        - default-headers
        - https-redirectscheme
      tls:
        certResolver: letsencrypt
      service: romm

  services:
    romm:
      loadBalancer:
        servers:
          - url: "http://192.168.1.100:8080"
        passHostHeader: true

Docker Compose labels

Add these to the romm service in your docker-compose.yml:

labels:
    - "traefik.enable=true"
    - "traefik.http.services.romm.loadbalancer.server.port=8080"
    - "traefik.http.routers.romm.rule=Host(`romm.mysite.com`)"
    - "traefik.http.routers.romm.entrypoints=websecure"
    - "traefik.http.routers.romm.tls=true"
    - "traefik.http.routers.romm.tls.certresolver=letsencrypt"

Nginx Proxy Manager

Items marked are important — RomM won't work right without them.

Details

  • Domain Names: romm.mysite.com
  • Scheme: http
  • Forward Hostname/IP: container hostname or LAN IP (e.g. 192.168.1.100)
  • Forward Port: 8080
  • Cache Assets: off
  • Block Common Exploits: on
  • Websockets Support: on
  • Access List: as needed

SSL

  • SSL Certificate: Request a new SSL Certificate
  • Force SSL: on
  • HTTP/2 Support: on
  • HSTS Enabled: on (after you've confirmed TLS works)
  • Email Address for Let's Encrypt: your address
  • I Agree to the TOS: on

Advanced — custom nginx configuration

proxy_max_temp_file_size 0;

Without that line, large downloads (bulk ROM zips, multi-disc games) will fail on NPM because nginx tries to buffer them to disk.

Details SSL Advanced
image image2 image3

Set ROMM_BASE_URL behind HTTPS

Once you're proxying through HTTPS, set ROMM_BASE_URL in the RomM container's environment so generated links (QR codes, invite links, OIDC redirects) use the public URL:

environment:
  - ROMM_BASE_URL=https://romm.mysite.com

If you're also using OIDC, update OIDC_REDIRECT_URI to match — see OIDC Setup.