Kioskgaming · Infrastructure · Reference drawings

System diagram & Nginx configuration

Cloudflare → nginx-lb (V1) → 5 internal servers — production reference drawing. Main conclusions and points to be addressed: section 03. Details of each floor:Deep Dive infrastructure.

Source
repo nginx/ + Hetzner Console
Update
2026-07-04
Scope
5 server Hetzner (Ashburn)
Full map
system-map.md
production traffic flow !points to be handled — see section 03

01Request flow & gateway location

User / Client app
HTTPS :443 → *.kioskservice.club
Cloudflare — DNS · CDN · SSL Flexible
TLS ends at Cloudflare attaches CF-Connecting-IP / CF-Ray / X-Forwarded-Proto

HTTP :80 origin-pull — see "Realip / Cloudflare trust" card below

nginx-lb (nginx:alpine) — system-wide single gateway (SPOF)
Run onV1-Kiosk· network_mode: host · routing by server_name
V1 · gateway+app
10.0.0.5
  • nginx-lb :80 ★
  • API prod :3001–3004
  • API dev :4001
  • Dozzle :8080
V2 · app
10.0.0.4
  • API prod :3001–3004
  • API dev :4001
  • Dozzle :8080
V3 · data master
10.0.0.2
  • Redis master :6379
  • Postgres master :5432
  • Dozzle :8080
V4 · tools+replica
10.0.0.3
  • Redis/PG slave, RabbitMQ, Redis dev
  • RedisInsight, OmniDB, Dozzle
V5 · gamify+log
10.0.0.6
  • Gamify pro/dev, OpenSearch
  • Dashboards, Fluent Bit
!

Operating notes:V2 and V3 share the same default hostnameubuntu-8gb-ash-1(Hetzner is named after the spec, not the unique identifier).Do not use the hostname to identify the server when SSH — only the IP can be sure: V2 = 10.0.0.4 / 178.156.166.189, V3 = 10.0.0.2 / 178.156.211.239.

02Nginx configuration illustrated step by step

① Cloudflare trust — realip

default.conf:28–43 (repeat every conf file)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
… (15 Cloudflare CIDR bands) …
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
Restore the client's real IP because all requests to the origin come from Cloudflare IP.

② Original Forward scheme

default.conf:46–54
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 $http_x_forwarded_proto;
proxy_set_header CF-Connecting-IP $http_cf_connecting_ip;
proxy_set_header CF-Ray $http_cf_ray;
SSL Flexible mode: origin only receives HTTP, so the backend must read this header to know that the origin session is HTTPS.

③ Upstream API production

default.conf:1–16
upstream backend {
    least_conn;
    server 10.0.0.5:3001 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.5:3002 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.5:3003 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.5:3004 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.4:3001 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.4:3002 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.4:3003 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.4:3004 weight=1 max_fails=3 fail_timeout=30s;
}
8 instances: 4 on V1 + 4 on V2. Passive health check: down after 3 errors / 30 seconds.

④ WebSocket (Dozzle / RedisInsight / Dashboards)

dozzle.conf, redisinsight.conf, app-logs.conf
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
These 3 services stream realtime data via WS — without these 3 lines, the UI freezes even though nginx is still 200 OK.

⑤ Core nginx.conf

nginx/nginx.conf:1–37
worker_processes auto;
worker_connections 1024;
keepalive_timeout 65;
gzip on;
gzip_comp_level 6;
include conf.d/*.conf;
Simultaneous connection ceiling ≈ number of cores × 1024. V1 (CCX23) = 4 cores → ~4096; V2/V4 (CCX13) = 2 cores → ~2048.

⑥ Container nginx-lb

nginx.yml:1–15
image: nginx:alpine
container_name: nginx-lb
restart: always
network_mode: host
volumes:
  - ./nginx/conf.d:/etc/nginx/conf.d:ro
network_mode: hostThis is the reason why nginx can directly call 10.0.0.x without having to declare a separate Docker network - and also the reason why if running 2 versions on 2 servers, they will NOT conflict with each other's ports (each is independent on its own host).

⑦ Hetzner Cloud Firewall — firewall-ssh

Console, applies to 5/5 servers
22/tcp   ← Any IPv4, Any IPv6
icmp     ← Any IPv4, Any IPv6
80/tcp ← 5/15 Cloudflare strips
(missing 10 strips nginx is trusting)
443/tcp ← 15/15 Cloudflare strips
(nginx not listening 443)
This is the network filtering layer BEFORE nginx — different fromset_real_ip_from(filtering at the application layer). The two levels are different at port 80: the firewall is tighter than nginx (5 bands compared to 15), so it can mistakenly block real Cloudflare traffic.firewall-sshis the only firewall in the project and there are no rules for 6379/5432/9200/… — default-deny data ports from the Internet.

03Key Conclusions & points to be handled

!

Point to be handled (Hetzner Firewallfirewall-ssh, applies to all 5 servers):rule :80 indicates whitelist5/15 Cloudflare bandswhile nginx believes 15 bands passedset_real_ip_from— real requests via PoP belonging to 10 missing bands are DROP before reaching nginx (edit on Console, step 0A of conversion plan); rule :22 opens to the entire Internet (step 0B); rule :443 has enough 15/15 ranges even if nginx doesn'tlisten 443. See card ⑦ above andrisks #13–14 in infrastructure-deep-dive.html.

Main conclusion: nginx-lb only on V1— SPOF of the entire system ·frontend is not in 5 servers: 6 site (client, shop, agent, super-agent, admin, cashier) hosting onCloudflare Pages· data ports (6379/5432/9200/…) default-deny from the Internet · Dozzle (4/4) and RedisInsight reportunhealthyjust the healthcheck image is wrong — not a problem. Processing plan:comparison-and-migration-plan.html· target architecture:target-system.html.