Index: .env.example
===================================================================
--- .env.example	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
+++ .env.example	(revision f99bbe8116e4b79bb4d4e5a814dca1b301133c64)
@@ -1,4 +1,10 @@
 NODE_ENV=development
 PORT=3000
+
+# Network interface to bind. 127.0.0.1 = only reachable via a reverse proxy on the
+# same machine (recommended for a manual install behind Caddy/nginx — keeps the app
+# off the public internet). Use 0.0.0.0 only if you need direct external access
+# (no proxy/HTTPS — not recommended). Docker sets this to 0.0.0.0 itself.
+HOST=127.0.0.1
 # Secret used to sign login-session cookies. Leave EMPTY to auto-generate a strong
 # one on first start (saved to storage/.session-secret, stays stable across
Index: README.md
===================================================================
--- README.md	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
+++ README.md	(revision f99bbe8116e4b79bb4d4e5a814dca1b301133c64)
@@ -115,4 +115,10 @@
 ```
 
+By default the app binds to `127.0.0.1` (via `HOST` in `.env`), so only your
+reverse proxy can reach it — not the open internet. Local testing on the same
+machine (`localhost:3000`) still works. Only set `HOST=0.0.0.0` if you need direct
+external access without a proxy (then open the port in your firewall and add HTTPS
+yourself).
+
 (`cwebp` is optional — `apt install webp` — for WebP image conversion.)
 
Index: docker-compose.yml
===================================================================
--- docker-compose.yml	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
+++ docker-compose.yml	(revision f99bbe8116e4b79bb4d4e5a814dca1b301133c64)
@@ -16,13 +16,17 @@
     env_file: .env
     environment:
-      # In de container draait 'ie altijd op 3000 en in productie-modus,
-      # ongeacht wat er in .env staat.
+      # Inside the container it always runs on port 3000 in production mode and
+      # binds all interfaces (so the port mapping works) — overrides .env. The
+      # loopback host-mapping below is what keeps it off the public internet.
       NODE_ENV: production
       PORT: "3000"
+      HOST: "0.0.0.0"
     ports:
-      # host:container — wijzig de host-poort (links) als 3000 al bezet is.
-      - "3000:3000"
+      # Bind the host port to loopback only — reach the app through your reverse
+      # proxy (Caddy/nginx) on this host, not directly from the internet.
+      # Change the left side if 3000 is taken, e.g. "127.0.0.1:3001:3000".
+      - "127.0.0.1:3000:3000"
     volumes:
-      # Alle data (database, geüploade media + audio) blijft hier bewaard.
+      # All data (database, uploaded media + audio) is kept here.
       - klonkt-data:/app/storage
 
Index: scripts/install.sh
===================================================================
--- scripts/install.sh	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
+++ scripts/install.sh	(revision f99bbe8116e4b79bb4d4e5a814dca1b301133c64)
@@ -160,4 +160,7 @@
     echo "NODE_ENV=production"
     echo "PORT=${KLONKT_PORT}"
+    # Bind to loopback only: Caddy (this host) reaches it; the internet cannot
+    # hit the app directly on its port, bypassing HTTPS.
+    echo "HOST=127.0.0.1"
     echo "SESSION_SECRET=${SECRET}"
     echo "DATABASE_PATH=./storage/database.sqlite"
@@ -168,9 +171,11 @@
   } > "$ENV"
   chown "$KLONKT_USER:$KLONKT_USER" "$ENV"; chmod 600 "$ENV"
-  ok "new .env (random SESSION_SECRET)"
+  ok "new .env (random SESSION_SECRET, app bound to 127.0.0.1)"
 else
   # sync the port in an existing .env with the chosen port
   if grep -q '^PORT=' "$ENV"; then sed -i "s/^PORT=.*/PORT=${KLONKT_PORT}/" "$ENV"; fi
-  ok "kept existing .env (port synced)"
+  # harden older installs: bind to loopback if not already configured
+  grep -q '^HOST=' "$ENV" || echo "HOST=127.0.0.1" >> "$ENV"
+  ok "kept existing .env (port synced, bound to 127.0.0.1)"
 fi
 
Index: src/server.js
===================================================================
--- src/server.js	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
+++ src/server.js	(revision f99bbe8116e4b79bb4d4e5a814dca1b301133c64)
@@ -87,4 +87,8 @@
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 const PORT = process.env.PORT || 3000;
+// Interface to bind. Default 0.0.0.0 (needed for Docker port-forwarding). Behind a
+// reverse proxy on the same host, set HOST=127.0.0.1 so the app is NOT reachable
+// directly from the internet (only via the proxy) — see README/install docs.
+const HOST = process.env.HOST || '0.0.0.0';
 const isDev = process.env.NODE_ENV !== 'production';
 
@@ -427,5 +431,5 @@
 });
 
-server.listen(PORT, () => {
+server.listen(PORT, HOST, () => {
   console.log('');
   console.log('🪶 Klonkt Beta');
