# 09 — WAN Exposure (Public Internet) > **Status:** server-side changes applied 2026-05-08. Owner router-side > port-forward still **TODO** — until the router rules are added, the > WAN A record points to a closed port and arrflix.s8n.ru remains LAN-only > in practice. This document covers the move from **LAN-only** to **public-internet** access for `arrflix.s8n.ru`. It records what was changed, what the owner must still do on their home router, the risks they accept by opening the service, and the rollback procedure. --- ## 1. What changed server-side (already applied) ### 1.1 Public DNS A record (Gandi LiveDNS) A record added under `s8n.ru`: | Field | Value | |-------|-------| | Name | `arrflix` | | Type | `A` | | Value | `82.31.156.86` (current home WAN IP) | | TTL | `300` | Applied via Gandi LiveDNS API: ```bash curl -X PUT "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \ -H "Authorization: Bearer $GANDIV5_PERSONAL_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"rrset_values":[""],"rrset_ttl":300}' ``` Verified with `dig @1.1.1.1 arrflix.s8n.ru +short` → `82.31.156.86`. The Pi-hole local override (`192.168.0.100 arrflix.s8n.ru`) is **kept in place** so LAN clients bypass NAT hairpin and hit the LAN IP directly — see `feedback_s8n_hosts_override.md` for why hairpin is problematic. Public users get the WAN IP via 1.1.1.1 / Gandi. ### 1.2 Traefik middleware change `/opt/docker/jellyfin/docker-compose.yml`: ```diff - "traefik.http.routers.jellyfin.middlewares=security-headers@file,no-guest@file" + "traefik.http.routers.jellyfin.middlewares=security-headers@file" ``` The `no-guest@file` middleware (which restricted source IPs to `127.0.0.0/8 + 192.168.0.0/24 + tailnet + 82.22.5.233`) is dropped from the Jellyfin router. `security-headers@file` is kept. Container recreated: ```bash ssh user@192.168.0.100 'cd /opt/docker/jellyfin && docker compose up -d' ``` ### 1.3 Brute-force lockout policy `POST /Users/{id}/Policy` applied with `LoginAttemptsBeforeLockout=5` on every existing user (`s8n` admin, `guest`, `5`). After 5 failed login attempts the user is locked out and only an admin can unlock. The wrapper `bin/add-jellyfin-user.sh` is updated to apply this automatically to every future user so we never forget it. --- ## 2. What the OWNER must still do (router-side) The home router controls whether traffic from the public internet reaches nullstone. Until the owner adds the rules below, the public DNS record points at a closed port. ### 2.1 Port-forward rules Two rules needed on the home router admin UI (typically reachable at `http://192.168.0.1` or similar — varies by ISP-supplied router): | External port | Protocol | Internal IP | Internal port | Purpose | |---------------|----------|------------------|---------------|---------| | 443 | TCP | `192.168.0.100` | 443 | Traefik HTTPS (main entrypoint) | | 80 | TCP | `192.168.0.100` | 80 | Traefik HTTP (LE redirect to HTTPS) | Generic steps (the labels vary by router brand): 1. Log into the home router admin UI. 2. Find the section called **Port Forwarding**, **Virtual Servers**, **NAT**, or **Applications & Gaming**. 3. Add a new rule: external port `443` TCP → internal host `192.168.0.100` port `443`. 4. Add a second rule: external port `80` TCP → internal host `192.168.0.100` port `80`. 5. Save / apply. 6. Reboot the router only if the UI doesn't apply rules live. ### 2.2 Do **NOT** forward - Port `22` (SSH) — never expose. Tailscale or LAN only. - Any port to anything other than `192.168.0.100`. ### 2.3 Verify from outside the LAN From a phone on mobile data (or any non-home network): ```bash curl -I https://arrflix.s8n.ru/health # expect: HTTP/2 200 ``` If you get a connection timeout or refused, the port-forward isn't live. --- ## 3. Risks the owner must accept By moving from LAN-only to public-internet: ### 3.1 Login page is on the open internet Anyone on the internet can hit the login form. Mitigation: `LoginAttemptsBeforeLockout=5` plus strong passwords. **There is no fail2ban or rate-limiting at the Traefik layer** — the lockout is purely application-level (Jellyfin's own counter). Consider adding a Traefik rate-limit middleware later if abuse shows up in logs. ### 3.2 Copyrighted media exposure The library contains commercial movies and TV. Hosting it on a public-resolvable domain raises legal heat (rights-holder C&D, ISP terms-of-service action). This is an information note, not legal advice — only the owner can decide whether to accept it. ### 3.3 Dynamic WAN IP (Virgin Media) Home WAN IPs on consumer ISPs change without notice. When the WAN IP changes, the Gandi A record must be refreshed or arrflix.s8n.ru will point at someone else's IP. A simple cron-driven updater (run every 15 min on nullstone): ```bash #!/usr/bin/env bash # /opt/scripts/gandi-arrflix-updater.sh # Refreshes arrflix.s8n.ru A record if WAN IP changed. set -euo pipefail TOKEN="$(grep ^GANDIV5_PERSONAL_ACCESS_TOKEN /opt/docker/traefik/.env | cut -d= -f2)" WAN=$(curl -s --max-time 10 https://ifconfig.me) [[ "$WAN" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1 CUR=$(dig @1.1.1.1 +short arrflix.s8n.ru | head -1) [[ "$WAN" == "$CUR" ]] && exit 0 curl -s -X PUT "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{\"rrset_values\":[\"$WAN\"],\"rrset_ttl\":300}" \ > /var/log/gandi-arrflix.log 2>&1 logger "gandi-arrflix-updater: changed $CUR -> $WAN" ``` Cron: ``` */15 * * * * /opt/scripts/gandi-arrflix-updater.sh ``` The Gandi token in `/opt/docker/traefik/.env` is already scoped to LiveDNS-only on `s8n.ru`, so even if the script leaks it can't change nameservers or add other domains (per `reference_gandi_api.md`). ### 3.4 **Weak password warning — read this** The new user `5` has password `1234`. This is **fine for a private LAN-only service shared with one friend**. It is **dangerous on the public internet**: - A botnet hits common username/password combos within hours of the domain becoming reachable. `5/1234` is in every basic password list. - The lockout fires after 5 attempts but a distributed attacker rotates source IPs faster than that. - Even if Jellyfin login holds, weak creds normalise weak creds — the next person added gets `6/1234` and the bar drops further. **Recommendation** before the owner opens the router: - Reset user `5`'s password to something strong (12+ random chars). - Reset the `guest` user's password as well (or disable it). - Consider whether the `s8n` admin account password is strong. - Owner decides whether to keep the `5/1234` username/pw memorable for the friend at the cost of being an obvious target. The owner accepted `5/1234` for the Forgejo friend account, but Forgejo on git.s8n.ru holds no copyrighted media. Jellyfin does. Different threat profile, same friend, different decision. --- ## 4. Rollback (revert to LAN-only) If the public exposure goes wrong (abuse, legal pressure, anything): 1. **Close the router ports.** Remove the port-forward rules for `443/tcp` and `80/tcp` on the home router admin UI. This is the fastest single-action shutoff — no internet traffic reaches Traefik. 2. **Re-add the no-guest middleware.** Edit `/opt/docker/jellyfin/docker-compose.yml`, change ```yaml - "traefik.http.routers.jellyfin.middlewares=security-headers@file" ``` back to ```yaml - "traefik.http.routers.jellyfin.middlewares=security-headers@file,no-guest@file" ``` then `cd /opt/docker/jellyfin && docker compose up -d`. Even if a port stays open by mistake, only LAN/tailnet/owner-IP hits succeed. 3. **Remove the Gandi A record.** ```bash curl -X DELETE "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \ -H "Authorization: Bearer $GANDIV5_PERSONAL_ACCESS_TOKEN" ``` Pi-hole's LAN override remains, so LAN clients keep working. 4. **Disable or delete the cron updater** if installed. 5. **Optional:** set `LoginAttemptsBeforeLockout` back to `-1` if the lockout is causing friction and there's no public exposure to defend against. (Not required — keeping `5` is harmless.) After steps 1–3 the service is back to its pre-exposure posture (LAN + tailnet + owner home IP only). --- ## 5. Change log | Date | Change | By | |------------|--------|----| | 2026-05-08 | Gandi A record `arrflix.s8n.ru → 82.31.156.86` (TTL 300) | server-side | | 2026-05-08 | Drop `no-guest@file` middleware on Jellyfin router | server-side | | 2026-05-08 | `LoginAttemptsBeforeLockout=5` on s8n / guest / 5 | server-side | | 2026-05-08 | Wrapper `bin/add-jellyfin-user.sh` bakes in lockout=5 | server-side | | TODO | Home router port-forward 443+80 → 192.168.0.100 | owner | | TODO | Strong passwords on user `5` and `guest` | owner | | TODO | (Optional) Gandi WAN-IP cron updater on nullstone | owner |