Absturzschleife des Proxy Managers: IP-Bereich-Abruf abschaltbar

Das Containerprotokoll eines Kunden zeigte, was hinter dem 502 steckte:
der Dienst stuerzt in einer Schleife ab.

  [IP Ranges] Fetching https://ip-ranges.amazonaws.com/ip-ranges.json
  Uncaught Error: Client network socket disconnected before secure TLS
                  connection was established
  ... node --abort_on_uncaught_exception ... index.js
  Starting backend ...          [und das immer wieder]

Beim Start holt der Proxy Manager IP-Bereiche von AWS und Cloudflare -
gebraucht wird das nur fuer Zugriffslisten mit deren Adressbereichen,
auf einem internen Server also nie. Der Code faengt Fehler dabei ab,
dieser kommt aber asynchron vom TLS-Socket und laeuft am catch vorbei;
weil node mit --abort_on_uncaught_exception laeuft, bricht der Prozess
ab, s6 startet neu, und nginx davor antwortet durchgehend mit 502.

Am Abbild nachgesehen und ausprobiert:

  - IP_RANGES_FETCH_ENABLED=false verhindert den Abruf; im Protokoll
    steht dann "IP Ranges fetch is disabled by environment variable",
    das Backend laeuft.
  - Scheitert der Abruf sauber (DNS-Fehler, Verbindung abgelehnt), faengt
    der Code ihn ab und der Dienst laeuft weiter - der Absturz haengt
    also wirklich an der gekappten TLS-Verbindung.
  - initTimer() laeuft unabhaengig vom Schalter und versucht es alle
    sechs Stunden erneut. Das steht als Hinweis samt extra_hosts in der
    README.

Neu: NPM_IP_RANGES_FETCH, Vorgabe false. Und die Fehlermeldung erkennt
den Fall selbst - sie zaehlt Startversuche und Ausnahmen im
Containerprotokoll und nennt den Schalter, statt den Benutzer weiter auf
"startet noch" warten zu lassen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-21 00:21:10 +02:00
co-authored by Claude Opus 5
parent 4078a9c1a6
commit 58308264c6
5 changed files with 167 additions and 5 deletions
+7
View File
@@ -140,6 +140,13 @@ NPM_ADMIN_PORT=81
# 127.0.0.1 trotzdem ueber den Proxy. Dann hier auf localhost stellen. # 127.0.0.1 trotzdem ueber den Proxy. Dann hier auf localhost stellen.
NPM_API_HOST=127.0.0.1 NPM_API_HOST=127.0.0.1
# Holt der Proxy Manager beim Start IP-Bereiche von AWS und Cloudflare?
# Gebraucht wird das nur fuer Zugriffslisten mit deren Adressbereichen.
# Vorgabe hier: false. In Netzen, die TLS aufbrechen oder Verbindungen
# kappen, stirbt der Dienst sonst an einer unbehandelten Ausnahme und
# startet endlos neu - sichtbar nur als "502 Bad Gateway".
NPM_IP_RANGES_FETCH=false
# ===================================================================== # =====================================================================
# TERMINAL-BROWSER (lokale Bedienung des Proxy Managers) # TERMINAL-BROWSER (lokale Bedienung des Proxy Managers)
# Fuer den Fall, dass Port 81 von aussen nicht erreichbar ist. # Fuer den Fall, dass Port 81 von aussen nicht erreichbar ist.
+57
View File
@@ -554,6 +554,7 @@ Die wichtigsten Schalter:
| `NPM_ENABLED` | `on`/`off` – soll der Proxy-Manager-Container überhaupt laufen? Siehe [12.4](#124-proxy-manager-ganz-abschalten-externer-nginx) | | `NPM_ENABLED` | `on`/`off` – soll der Proxy-Manager-Container überhaupt laufen? Siehe [12.4](#124-proxy-manager-ganz-abschalten-externer-nginx) |
| `NPM_ADMIN_PORT` | Port der Proxy-Manager-Oberfläche (Vorgabe 81) – nur über `npm-port` bzw. das Menü ändern | | `NPM_ADMIN_PORT` | Port der Proxy-Manager-Oberfläche (Vorgabe 81) – nur über `npm-port` bzw. das Menü ändern |
| `NPM_API_HOST` | Name für die Schnittstelle (`127.0.0.1` oder `localhost`) – siehe [Kapitel 14.6](#146-netze-mit-zwangsproxy-api-fehler-der-keiner-ist) | | `NPM_API_HOST` | Name für die Schnittstelle (`127.0.0.1` oder `localhost`) – siehe [Kapitel 14.6](#146-netze-mit-zwangsproxy-api-fehler-der-keiner-ist) |
| `NPM_IP_RANGES_FETCH` | IP-Bereiche von AWS/Cloudflare holen (Vorgabe `false`) – siehe [Kapitel 10.1.2](#1012-der-dienst-stürzt-in-einer-schleife-ab) |
| `NPM_ADMIN_URL` | Startseite des Terminal-Browsers; leer = automatisch aus `NPM_ADMIN_PORT` | | `NPM_ADMIN_URL` | Startseite des Terminal-Browsers; leer = automatisch aus `NPM_ADMIN_PORT` |
| `BACKUP_DIR` | Ablage für **manuelle** Backups (Vorgabe `./backups`); geplante Jobs haben eigene Pfade | | `BACKUP_DIR` | Ablage für **manuelle** Backups (Vorgabe `./backups`); geplante Jobs haben eigene Pfade |
| `BACKUP_AUTO` | `on`/`off` – sollen die Backup-Jobs per cron laufen? | | `BACKUP_AUTO` | `on`/`off` – sollen die Backup-Jobs per cron laufen? |
@@ -1254,6 +1255,62 @@ man erkennt, wer da geantwortet hat:
<html><head><title>502 Proxy Error</title></head>... <html><head><title>502 Proxy Error</title></head>...
``` ```
### 10.1.2 Der Dienst stürzt in einer Schleife ab
Sieht das Protokoll des Containers so aus, ist der `502` erklärt:
```
[IP Ranges] › info Fetching https://ip-ranges.amazonaws.com/ip-ranges.json
Uncaught Error: Client network socket disconnected before secure TLS connection was established
at TLSSocket.emit (node:events:497:7)
... node --abort_on_uncaught_exception ... index.js
❯ Starting backend ...
[und das immer wieder]
```
Der Proxy Manager holt beim Start IP-Bereiche von AWS und Cloudflare –
gebraucht wird das **nur** für Zugriffslisten mit deren Adressbereichen, in
einem internen Netz also nie. Der Code fängt Fehler dabei ab, aber dieser
kommt asynchron vom TLS-Socket und läuft am `catch` vorbei. Weil node mit
`--abort_on_uncaught_exception` läuft, bricht der Prozess ab, s6 startet ihn
neu – und nginx davor antwortet die ganze Zeit mit `502`.
Ausgelöst wird das von Netzen, die die Verbindung **annehmen und dann kappen**
(aufbrechende Proxys, Firewalls). Scheitert der Abruf sauber – DNS-Fehler,
Verbindung abgelehnt –, passiert nichts; der Dienst läuft weiter.
Abhilfe:
```bash
./scripts/cmk-manage.sh config set NPM_IP_RANGES_FETCH false
./scripts/cmk-manage.sh up
```
Im Protokoll steht dann `IP Ranges fetch is disabled by environment variable`.
**Vorgabe in diesem Projekt ist `false`** – der Abruf bringt einem internen
Monitoring-Server nichts und kann ihn lahmlegen.
> Ein Rest bleibt: `initTimer()` läuft unabhängig vom Schalter und versucht es
> **alle sechs Stunden** erneut. Soll auch das ausgeschlossen sein, zeigen die
> Namen ins Leere – dann scheitert der Abruf sauber und wird abgefangen:
>
> ```yaml
> extra_hosts:
> - "ip-ranges.amazonaws.com:127.0.0.1"
> - "www.cloudflare.com:127.0.0.1"
> ```
Die Fehlermeldung erkennt den Fall übrigens selbst – sie zählt Startversuche
und Ausnahmen im Containerprotokoll:
```
Der Dienst startet nicht nur - er stuerzt in einer Schleife ab.
Im Protokoll: 4 Startversuche, 4 unbehandelte Ausnahmen.
Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und
Cloudflare. ... Abschalten:
cmk-manage.sh config set NPM_IP_RANGES_FETCH false
```
### 10.2 Proxy-Host anlegen – ohne Browser ### 10.2 Proxy-Host anlegen – ohne Browser
Die Weboberfläche lässt sich im Terminal-Browser nur mühsam bedienen; Formulare Die Weboberfläche lässt sich im Terminal-Browser nur mühsam bedienen; Formulare
+57
View File
@@ -557,6 +557,7 @@ The most important settings:
| `NPM_ENABLED` | `on`/`off` – should the Proxy Manager container run at all? See [12.4](#124-turning-the-proxy-manager-off-entirely-external-nginx) | | `NPM_ENABLED` | `on`/`off` – should the Proxy Manager container run at all? See [12.4](#124-turning-the-proxy-manager-off-entirely-external-nginx) |
| `NPM_ADMIN_PORT` | port of the Proxy Manager interface (default 81) – change only via `npm-port` or the menu | | `NPM_ADMIN_PORT` | port of the Proxy Manager interface (default 81) – change only via `npm-port` or the menu |
| `NPM_API_HOST` | name the API is addressed by (`127.0.0.1` or `localhost`) – see [chapter 14.6](#146-networks-with-a-mandatory-proxy-an-api-error-that-is-none) | | `NPM_API_HOST` | name the API is addressed by (`127.0.0.1` or `localhost`) – see [chapter 14.6](#146-networks-with-a-mandatory-proxy-an-api-error-that-is-none) |
| `NPM_IP_RANGES_FETCH` | fetch IP ranges from AWS/Cloudflare (default `false`) – see [chapter 10.1.2](#1012-the-service-crashes-in-a-loop) |
| `NPM_ADMIN_URL` | start page of the terminal browser; empty = derived from `NPM_ADMIN_PORT` | | `NPM_ADMIN_URL` | start page of the terminal browser; empty = derived from `NPM_ADMIN_PORT` |
| `BACKUP_DIR` | location for **manual** backups (default `./backups`); scheduled jobs have their own paths | | `BACKUP_DIR` | location for **manual** backups (default `./backups`); scheduled jobs have their own paths |
| `BACKUP_AUTO` | `on`/`off` – should the backup jobs run via cron? | | `BACKUP_AUTO` | `on`/`off` – should the backup jobs run via cron? |
@@ -1251,6 +1252,62 @@ line, which reveals who answered:
<html><head><title>502 Proxy Error</title></head>... <html><head><title>502 Proxy Error</title></head>...
``` ```
### 10.1.2 The service crashes in a loop
If the container log looks like this, the `502` is explained:
```
[IP Ranges] › info Fetching https://ip-ranges.amazonaws.com/ip-ranges.json
Uncaught Error: Client network socket disconnected before secure TLS connection was established
at TLSSocket.emit (node:events:497:7)
... node --abort_on_uncaught_exception ... index.js
❯ Starting backend ...
[over and over]
```
At startup the Proxy Manager fetches IP ranges from AWS and Cloudflare – needed
**only** for access lists using those address ranges, so never on an internal
network. The code does catch errors there, but this one arrives asynchronously
on the TLS socket and bypasses the `catch`. Since node runs with
`--abort_on_uncaught_exception`, the process aborts, s6 restarts it – and nginx
in front answers `502` the whole time.
It is triggered by networks that **accept the connection and then cut it**
(TLS-intercepting proxies, firewalls). If the fetch fails cleanly – DNS error,
connection refused – nothing happens; the service keeps running.
The fix:
```bash
./scripts/cmk-manage.sh config set NPM_IP_RANGES_FETCH false
./scripts/cmk-manage.sh up
```
The log then says `IP Ranges fetch is disabled by environment variable`.
**This project defaults to `false`** – the fetch gains an internal monitoring
server nothing and can take it down.
> One remainder: `initTimer()` runs regardless of the switch and retries **every
> six hours**. To rule that out as well, point the names nowhere – then the
> fetch fails cleanly and is caught:
>
> ```yaml
> extra_hosts:
> - "ip-ranges.amazonaws.com:127.0.0.1"
> - "www.cloudflare.com:127.0.0.1"
> ```
The error message recognises this case by itself – it counts start attempts and
exceptions in the container log:
```
Der Dienst startet nicht nur - er stuerzt in einer Schleife ab.
Im Protokoll: 4 Startversuche, 4 unbehandelte Ausnahmen.
Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und
Cloudflare. ... Abschalten:
cmk-manage.sh config set NPM_IP_RANGES_FETCH false
```
### 10.2 Creating a proxy host – without a browser ### 10.2 Creating a proxy host – without a browser
The web interface is painful in a terminal browser, and forms with file uploads The web interface is painful in a terminal browser, and forms with file uploads
+8
View File
@@ -68,6 +68,14 @@ services:
# REST-API laesst sich nicht ohne Weiteres nutzen. # REST-API laesst sich nicht ohne Weiteres nutzen.
INITIAL_ADMIN_EMAIL: ${NPM_ADMIN_EMAIL:-} INITIAL_ADMIN_EMAIL: ${NPM_ADMIN_EMAIL:-}
INITIAL_ADMIN_PASSWORD: ${NPM_ADMIN_PASSWORD:-} INITIAL_ADMIN_PASSWORD: ${NPM_ADMIN_PASSWORD:-}
# Beim Start holt der Proxy Manager sonst IP-Bereiche von AWS und
# Cloudflare. Gebraucht wird das nur fuer Zugriffslisten mit deren
# Adressbereichen - in einem internen Netz also nie. Wird die
# TLS-Verbindung dabei aufgebaut und dann gekappt (aufbrechender
# Proxy, Firewall), stirbt der Dienst an einer unbehandelten
# Ausnahme und startet in einer Schleife neu; nach aussen sieht man
# nur "502 Bad Gateway".
IP_RANGES_FETCH_ENABLED: ${NPM_IP_RANGES_FETCH:-false}
volumes: volumes:
- ${NPM_DATA_DIR:-./data/npm/data}:/data:Z - ${NPM_DATA_DIR:-./data/npm/data}:/data:Z
- ${NPM_LE_DIR:-./data/npm/letsencrypt}:/etc/letsencrypt:Z - ${NPM_LE_DIR:-./data/npm/letsencrypt}:/etc/letsencrypt:Z
+38 -5
View File
@@ -3309,6 +3309,7 @@ npm|NPM_ADMIN_EMAIL|text|Konto des Proxy Managers (E-Mail)||1
npm|NPM_ADMIN_PASSWORD|password|Kennwort des Proxy-Manager-Kontos||1 npm|NPM_ADMIN_PASSWORD|password|Kennwort des Proxy-Manager-Kontos||1
npm|NPM_ADMIN_PORT|port|Port der Adminoberflaeche|| npm|NPM_ADMIN_PORT|port|Port der Adminoberflaeche||
npm|NPM_API_HOST|text|Name fuer die Schnittstelle (127.0.0.1 oder localhost)||1 npm|NPM_API_HOST|text|Name fuer die Schnittstelle (127.0.0.1 oder localhost)||1
npm|NPM_IP_RANGES_FETCH|choice|IP-Bereiche von AWS/Cloudflare holen|false:true|1
npm|PROXY_DOMAIN|text|Vorgabe-Domain fuer Proxy-Hosts||1 npm|PROXY_DOMAIN|text|Vorgabe-Domain fuer Proxy-Hosts||1
npm|PROXY_FORWARD_HOST|text|Weiterleitungsziel (Host)|| npm|PROXY_FORWARD_HOST|text|Weiterleitungsziel (Host)||
npm|PROXY_FORWARD_PORT|port|Weiterleitungsziel (Port)|| npm|PROXY_FORWARD_PORT|port|Weiterleitungsziel (Port)||
@@ -3445,7 +3446,7 @@ settings_post_set() {
cmd_cert_external --watch "$val" ;; cmd_cert_external --watch "$val" ;;
CERT_EXTERNAL_DIR) CERT_EXTERNAL_DIR)
log "Wirksam nach: $SELF up (das Verzeichnis wird erst dann eingehaengt)" ;; log "Wirksam nach: $SELF up (das Verzeichnis wird erst dann eingehaengt)" ;;
CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR) CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR|NPM_IP_RANGES_FETCH)
log "Wirksam nach: $SELF up (Container werden neu erstellt)" ;; log "Wirksam nach: $SELF up (Container werden neu erstellt)" ;;
esac esac
} }
@@ -3771,6 +3772,37 @@ json_str() { printf '%s' "$1" | tr ',' '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[:
# certificate_id oder owner_user_id zu, weil dort ein _ vor dem id steht. # certificate_id oder owner_user_id zu, weil dort ein _ vor dem id steht.
json_ids() { printf '%s' "$1" | grep -o '"id":[0-9]*' | sed 's/.*://' ; } json_ids() { printf '%s' "$1" | grep -o '"id":[0-9]*' | sed 's/.*://' ; }
# Stirbt der Dienst hinter nginx in einer Schleife? Das sieht von aussen
# aus wie "startet noch", hoert aber nie auf. Im Protokoll steht es.
npm_diagnose_backend() {
container_running "$NPM_CONTAINER_NAME" || return 0
local log starts crash
log="$($RUNTIME logs --tail 200 "$NPM_CONTAINER_NAME" 2>&1 || true)"
starts="$(printf '%s\n' "$log" | grep -c 'Starting backend' || true)"
crash="$(printf '%s\n' "$log" | grep -c 'Uncaught Error' || true)"
[ "${starts:-0}" -ge 3 ] || [ "${crash:-0}" -ge 2 ] || return 0
printf '
%sDer Dienst startet nicht nur - er stuerzt in einer Schleife ab.%s
Im Protokoll: %s Startversuche, %s unbehandelte Ausnahmen.\n' \
"$C_YEL" "$C_RST" "${starts:-0}" "${crash:-0}" >&2
case "$log" in
*ip-ranges.amazonaws.com*|*"IP Ranges"*)
printf '
Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und
Cloudflare. In Netzen, die TLS aufbrechen oder Verbindungen kappen,
faellt der Dienst darueber - und startet endlos neu. Abschalten:
%s config set NPM_IP_RANGES_FETCH false
%s up
Gebraucht wird der Abruf nur fuer Zugriffslisten mit den
Adressbereichen von AWS oder Cloudflare.\n' "$SELF" "$SELF" >&2 ;;
*)
printf ' Letzte Ausnahme im Protokoll:
%s\n' "$(printf '%s\n' "$log" | grep -A1 'Uncaught Error' | tail -1 | cut -c1-70)" >&2 ;;
esac
}
npm_api_token() { npm_api_token() {
local base; base="$(npm_api_base)" local base; base="$(npm_api_base)"
if [ -z "${NPM_ADMIN_EMAIL:-}" ] || [ -z "${NPM_ADMIN_PASSWORD:-}" ]; then if [ -z "${NPM_ADMIN_EMAIL:-}" ] || [ -z "${NPM_ADMIN_PASSWORD:-}" ]; then
@@ -3911,11 +3943,12 @@ EOF
printf ' Der Proxy Manager selbst hat geantwortet (%s) - aber der printf ' Der Proxy Manager selbst hat geantwortet (%s) - aber der
Dienst DAHINTER ist nicht erreichbar. Das ist normal, solange er Dienst DAHINTER ist nicht erreichbar. Das ist normal, solange er
noch startet: nach einem Neustart dauert es rund zehn Sekunden, noch startet: nach einem Neustart dauert es rund zehn Sekunden,
bis die Schnittstelle antwortet. bis die Schnittstelle antwortet.\n' "$srv" >&2
npm_diagnose_backend
Kurz warten und noch einmal, sonst ins Protokoll sehen: printf '
Sonst ins Protokoll sehen:
%s logs %s logs
%s proxy restart\n' "$srv" "$SELF" "$SELF" >&2 %s proxy restart\n' "$SELF" "$SELF" >&2
else else
printf ' Die Antwort kam ueber einen Proxy (Via: %s), nicht direkt. printf ' Die Antwort kam ueber einen Proxy (Via: %s), nicht direkt.
Pruefen: %s noproxy\n' "$via" "$SELF" >&2 Pruefen: %s noproxy\n' "$via" "$SELF" >&2