Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 30.10.2026 · 9 min Lesezeit

Docker auf dem netcup-vServer: Warum ufw nicht reicht und wie Sie Ports richtig freigeben

Teil 16 der netcup-Serie: Veröffentlichte Docker-Ports laufen an ufw vorbei. Die getestete Anleitung zeigt das Problem und die Lösung mit SCP-Firewall, Bindung an 127.0.0.1 und der Kette DOCKER-USER.

Geprüft am 30.10.2026 · für Docker Engine 29.9.2, ufw 0.36.2, nginx 1.31.8

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Titelbild: Docker auf dem netcup-Server mit Tor vor den Containern, Teil 16 der netcup-Serie

Dies ist Teil 16 unserer netcup-Serie. Er baut auf der SCP-Firewall aus Teil 5 und dem REST-API-Teil 15 auf, weiter geht es mit dem Reverse Proxy in Teil 17.

Wer auf einem netcup-vServer Docker installiert, ufw aktiviert und danach einen Container mit ports: "8080:80" startet, hat oft einen offenen Port, ohne es zu merken: Docker schreibt eigene Firewall-Regeln und läuft damit an ufw vorbei. ufw status zeigt davon nichts. Diese Anleitung macht das Problem sichtbar und zeigt die saubere Lösung aus drei Schichten: SCP-Firewall vor dem Server, Ports nur an 127.0.0.1 binden, und Regeln in der Kette DOCKER-USER für Ausnahmen. Im Labor getestet mit Docker Engine 29.9.2, Compose v5.6.1 und ufw 0.36.2 in einem Debian-13.7-Container. Die Angaben zur SCP-Firewall stammen aus der netcup-Dokumentation.

Voraussetzungen

  • netcup-vServer oder Root-Server mit Debian 13, eingerichtet nach den ersten Schritten aus Teil 2
  • Als Richtwert für einen kleinen Docker-Server: 2 Kerne, 4 GB RAM und 20 GB freier Speicher
  • SSH-Zugang mit sudo-Rechten und Zugang zum Server Control Panel (SCP)
  • Ein zweiter Rechner für Tests von außen, zum Beispiel Ihr Arbeitsplatz

Schritt 1: Docker installieren

Nutzen Sie die offizielle Paketquelle von Docker, nicht das ältere Distributionspaket. Die Einzelheiten stehen in unserer Docker-Grundlage. Für Debian 13 sehen die Befehle so aus:

sudo apt-get update
sudo apt-get install -y ca-certificates curl ufw
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verifizieren: docker --version und docker compose version nennen die installierten Versionen, im Test 29.9.2 und v5.6.1.

Schritt 2: ufw einrichten

Haben Sie ufw schon nach Teil 5 eingerichtet, überspringen Sie diesen Schritt: Dort sind auch ausgehende Regeln und ufw limit gesetzt, die folgenden Befehle würden das ändern.

Sonst richten Sie ufw so ein, dass eingehend alles gesperrt ist und nur SSH durchkommt. Nutzt Ihr SSH einen anderen Port, tragen Sie unbedingt diesen ein, sonst sperren Sie sich aus. Dann hilft nur noch die Konsole im Reiter Bildschirm des SCP. Die Grundlagen zu ufw erklärt die Anleitung Linux-Server absichern.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

Verifizieren: Die Ausgabe enthält Status: active und nur die Regeln für Port 22. Ein Dienst ohne Docker wäre jetzt von außen gesperrt: Im Test lieferte ein kleiner Webserver auf Port 9000 lokal 200, von außen kam keine Antwort.

Schritt 3: Das Problem sichtbar machen

Legen Sie einen Beispieldienst an, zunächst mit der üblichen, aber unsicheren Port-Angabe. Führen Sie das nur auf einem Server ohne wichtige Dienste aus und nur für diesen Versuch. Ist bereits eine SCP-Policy wie in Teil 5 zugewiesen, gibt sie Port 8080 nicht frei und Sie sehen in diesem Schritt einen Timeout. Der Versuch zeigt das Verhalten von ufw deshalb nur, wenn Port 8080 in der SCP-Firewall frei ist.

sudo mkdir -p /opt/app/html
cd /opt/app
echo "<h1>Marker 4711</h1>" | sudo tee html/index.html
sudo tee compose.yaml >/dev/null <<'EOF'
services:
  web:
    image: nginx:1.30.6
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
EOF
sudo docker compose up -d

Prüfen Sie nun von Ihrem Arbeitsplatz aus:

curl -s -o /dev/null -m 6 -w "%{http_code}\n" http://SERVER-IP:8080/

Im Test antwortete der Dienst mit 200, obwohl ufw eingehend alles sperrt. Auf dem Server zeigt sudo docker compose ps --format "{{.Name}} {{.Status}} {{.Ports}}":

app-web-1 Up 3 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp

Der Grund: Pakete an veröffentlichte Container-Ports laufen durch die Weiterleitung und erreichen die ufw-Regeln für eingehenden Verkehr gar nicht. Docker legt dafür eine eigene Kette an. sudo iptables -L DOCKER -n zeigt die Freigabe mit dem Container-Port (dpt:80), sudo ufw status erwähnt Port 8080 nicht. Die Docker-Dokumentation nennt beide Werkzeuge ausdrücklich unverträglich.

Verifizieren: sudo ufw status | grep -c 8080 gibt 0 aus, der Aufruf von außen trotzdem 200.

Schritt 4: Ausnahmen in der Kette DOCKER-USER

Der Beispieldienst ist noch offen veröffentlicht, das passt zum nächsten Versuch. Muss ein Container offen bleiben, gehört die Einschränkung in die Kette DOCKER-USER. Docker wertet sie vor den eigenen Regeln aus und lässt sie unverändert. Ermitteln Sie zuerst die Schnittstelle mit ip route show default. Im Beispiel heißt sie eth0:

sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 -j DROP
sudo iptables -L DOCKER-USER -n -v

Löschen Sie die Regel nach dem Versuch wieder:

sudo iptables -D DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 -j DROP

Wichtig ist --ctorigdstport: Bis die Pakete diese Kette erreichen, hat Docker die Zieladresse bereits umgeschrieben. Der ursprüngliche Port steht nur noch im Verbindungs-Tracking. Im Test war der offen veröffentlichte Port damit von außen gesperrt, nach dem Löschen der Regel mit iptables -D wieder erreichbar.

So eingetragene Regeln sind nicht dauerhaft gespeichert. Sie müssten beim Systemstart neu gesetzt werden, deshalb ist die Bindung an 127.0.0.1 für den Alltag die bessere Wahl.

Verifizieren: Solange die Regel steht, bekommt der Aufruf vom Arbeitsplatz keine Antwort. Nach dem Löschen liefert er wieder 200.

Schritt 5: Ports an 127.0.0.1 binden

Die einfachste Lösung ist die Bindung an die Loopback-Adresse. Der Dienst ist dann nur noch auf dem Server selbst erreichbar, ein Reverse Proxy nimmt die Verbindungen von außen an (Teil 17).

cd /opt/app
sudo sed -i 's/- "8080:80"/- "127.0.0.1:8080:80"/' compose.yaml
sudo docker compose up -d

Machen Sie das zur festen Gewohnheit: In jeder compose.yaml steht vor dem Port die Adresse 127.0.0.1, außer bei dem einen Dienst, der wirklich von außen erreichbar sein soll. Datenbanken und Admin-Oberflächen veröffentlichen Sie am besten gar nicht.

Verifizieren: sudo docker compose ps zeigt 127.0.0.1:8080->80/tcp, curl -s http://127.0.0.1:8080/ auf dem Server liefert den Marker, und der Aufruf vom Arbeitsplatz endet ohne Antwort.

Schritt 6: Die SCP-Firewall als äußere Schicht

Die SCP-Firewall filtert laut netcup vor dem Server. Docker kann sie deshalb nicht aushebeln, ein versehentlich offener Port bleibt dort hängen. Legen Sie eine Policy wie in Teil 5 an: SSH nur von Ihrer festen IP oder VPN-Adresse, dazu 80 und 443, sobald ein Reverse Proxy läuft. Mehr nicht. Haben Sie keine feste Adresse, sperrt Sie eine IP-Beschränkung beim nächsten Wechsel aus. Legen Sie die SSH-Regel als Erstes an, denn ab der ersten eigenen Eingangsregel wird alles andere verworfen.

Im Betrieb gilt: Richten Sie die Policy ein, bevor der erste Container Ports veröffentlicht. Der Versuch aus Schritt 3 ist die bewusste Ausnahme. Beachten Sie auch, dass die SCP-Firewall laut netcup nur für TCP stateful arbeitet. Für Dienste über UDP, etwa WireGuard, brauchen Sie eine passende eingehende Regel, und sobald Sie ausgehend filtern, auch die Gegenrichtung.

Verifizieren: Im Reiter Firewall steht die Policy in der Liste, und es erscheint kein Hinweis auf ungespeicherte Änderungen. Ein Timeout auf Port 8080 allein beweist hier nichts, denn dieser Port ist schon durch die Bindung an 127.0.0.1 gesperrt.

Schritt 7: Sichern und wiederherstellen

Sichern Sie vor jeder Änderung den Projektordner mit compose.yaml und allen Daten darin. Benannte Docker-Volumes liegen nicht im Projektordner, sie brauchen eine eigene Sicherung. Kopieren Sie das Archiv anschließend vom Server weg, sonst verlieren Sie es bei einem Totalverlust mit. Ein Snapshot aus Teil 8 ergänzt das, ersetzt aber keine Sicherung außerhalb des Servers (Teil 18).

sudo tar czf /root/app-backup.tar.gz -C /opt app
sudo tar tzf /root/app-backup.tar.gz

Üben Sie die Wiederherstellung einmal mit dem Beispieldienst. Dieser Block löscht den Ordner absichtlich als Probe:

cd /opt/app && sudo docker compose down
cd /
sudo rm -rf /opt/app
sudo tar xzf /root/app-backup.tar.gz -C /opt
cd /opt/app && sudo docker compose up -d
sleep 3

Achtung: rm -rf löscht den Ordner endgültig, prüfen Sie vorher das Archiv. Nach einem echten Verlust spielen Sie auf dem neuen Server nur das Archiv ein und starten den Dienst. Im Test kam nach dem Löschen der Marker zurück.

Verifizieren: curl -s http://127.0.0.1:8080/ gibt <h1>Marker 4711</h1> aus.

Schritt 8: Container aktualisieren

Tragen Sie die neue Version in der compose.yaml ein, ziehen Sie das Image und starten Sie neu. Die Sicherung aus Schritt 7 ist Ihr Rückweg: Sie enthält auch die alte Version in der Compose-Datei, im Test stand nach dem Restore wieder nginx 1.30.6.

cd /opt/app
sudo sed -i 's/nginx:1.30.6/nginx:1.31.8/' compose.yaml
sudo docker compose pull -q
sudo docker compose up -d
sleep 3
sudo docker compose exec -T web nginx -v

Wie Sie sich automatisch über neue Versionen informieren lassen, zeigt die Anleitung zu Container-Updates.

Verifizieren: Die Ausgabe lautet nginx version: nginx/1.31.8, und der Marker ist weiter da.

Troubleshooting

SymptomUrsache und Lösung
Port trotz ufw von außen offenDer Container veröffentlicht den Port ohne Adresse. Binden Sie ihn an 127.0.0.1 (Schritt 5).
Bind for 127.0.0.1:8081 failed: port is already allocatedEin anderer Container oder Dienst nutzt den Port. Prüfen Sie mit sudo ss -tlnp und sudo docker ps, geben Sie den Port frei oder wählen Sie einen anderen.
Dienst von außen nicht erreichbar, obwohl der Port offen veröffentlicht istPrüfen Sie die SCP-Firewall: Sie filtert vor dem Server. Ein Timeout spricht für ein verworfenes Paket.
Test auf dem Server klappt, von außen nichtDas ist bei Bindung an 127.0.0.1 gewollt. Testen Sie Freigaben immer vom zweiten Rechner aus.
DOCKER-USER-Regel greift nichtFalsche Schnittstelle oder --dport statt --ctorigdstport. Prüfen Sie ip route show default.

Häufige Fragen

Kann ich auf ufw verzichten?

Nein. ufw schützt alle Dienste, die nicht in Docker laufen. Für veröffentlichte Docker-Ports dürfen Sie sich aber nicht darauf verlassen.

Reicht die SCP-Firewall allein?

Sie fängt offene Ports ab, solange die Policy zugewiesen ist. Ein Fehler in der Policy oder eine ausgeschaltete Firewall legt dann jeden offen veröffentlichten Port frei. Die Bindung an 127.0.0.1 schützt auch in diesem Fall.

Gilt das auch für IPv6?

Docker veröffentlichte den Port im Test ebenfalls unter [::]:8080. Die Regel aus Schritt 4 gilt mit iptables nur für IPv4, für IPv6 ist ip6tables zuständig. Prüfen Sie den Port deshalb von außen auch über die IPv6-Adresse. Bei Bindung an 127.0.0.1 entfällt das Problem.

Fazit

Auf einem netcup-Server gilt für Docker: erst die SCP-Firewall, dann Container, und jeden Port an 127.0.0.1 binden, solange er nicht öffentlich sein muss. ufw status sagt nichts über Docker-Ports aus, verlässlich ist nur der Test von außen.

Weiterführende Anleitungen und Quellen

netcupDockerufwFirewallvServeriptables