Ein offener und vergessener Port ist der klassische Weg in einen Server: eine Datenbank auf einer öffentlichen Adresse, ein Debug-Dashboard, ein Container, der einen Port veröffentlicht, den niemand veröffentlichen wollte. Die Prüfung dauert eine Minute, wenn du zwei Befehle und eine Falle kennst. Alles unten haben wir auf einem frischen Ubuntu-24.04-Server aus unserem Image ausgeführt und ihn von einem zweiten Rechner gescannt, die Ausgaben siehst du also genauso auf deinem eigenen Server.
1. Was lauscht
Der Befehl ist ss. Die Buchstaben stehen für TCP, UDP, lauschende Sockets, numerische Ports und den Prozessnamen:
sudo ss -tulpn
Auf einem frischen Server gibt er sechs Zeilen aus:
Netid State Local Address:Port Process
udp UNCONN 127.0.0.54:53 systemd-resolve
udp UNCONN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 127.0.0.54:53 systemd-resolve
tcp LISTEN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 0.0.0.0:22 sshd
tcp LISTEN [::]:22 sshd
Entscheidend ist die Spalte Local Address. Sie sagt dir, wer sich verbinden kann:
| Adresse | Wer sie erreicht |
|---|---|
127.0.0.1:… oder 127.0.0.53 | Nur Programme auf diesem Server |
0.0.0.0:… | Jeder über IPv4, sofern die Firewall nicht blockt |
[::]:… | Jeder über IPv6 (meist auch IPv4) |
10.0.0.5:… (eine bestimmte Adresse) | Jeder, der diese Adresse erreicht |
Der frische Server zeigt nach außen also genau eine Sache: SSH auf Port 22, über IPv4 und IPv6. Der DNS-Resolver auf 127.0.0.53 ist privat.
Um nur das aufzulisten, was das Internet sehen könnte, filtere die Loopback-Adressen heraus:
sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'
Führe das nach jeder Installation aus. Wir haben nginx, Docker und eine kleine Python-App installiert, und die Liste wuchs von 2 auf 10 Zeilen, darunter ein docker-proxy auf 0.0.0.0:8081 und ein containerd-Socket auf einem zufälligen localhost-Port.
2. Welcher Prozess einen Port belegt
Wenn ein Port belegt ist und du wissen willst, von wem:
sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'
lsof ist am lesbarsten. Für nginx zeigte es einen Master-Prozess als root und einen Worker als www-data, beide auf *:80, für IPv4 und IPv6. fuser gibt nur die PIDs aus, praktisch in Skripten. Wenn du den Port freimachen musst, stoppe den Dienst statt die PID zu killen, sonst startet systemd ihn neu: sudo systemctl stop nginx.
3. Was das Internet sieht
ss zeigt die Sicht des Servers. Die Sicht des Internets kann abweichen, weil eine Firewall dazwischensitzt oder weil ein Dienst an 127.0.0.1 gebunden ist. Für das echte Bild scanne den Server von einem anderen Rechner, deinem Laptop oder einem zweiten Server. Das Werkzeug ist nmap (apt install nmap, für Windows und macOS der Installer von nmap.org).
nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip
Die erste Form prüft die 1.000 häufigsten Ports, die zweite alle 65.535, die dritte eine Liste. Auf unserem frischen Server, noch ohne Firewall:
Not shown: 999 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds
Der vollständige Scan aller 65.535 Ports dauerte 8,9 Sekunden und fand denselben einzigen Port. Geschlossene Ports antworten sofort mit einem Reset, deshalb geht das Scannen so schnell.
Drei Zustände zählen:
- open: ein Programm hat die Verbindung angenommen. Das ist deine Angriffsfläche.
- closed: der Server hat geantwortet, dass dort nichts lauscht. Sicher, bestätigt aber, dass der Server existiert und was er nicht betreibt.
- filtered: keine Antwort. Eine Firewall hat das Paket verworfen. Der Scanner kann nicht sagen, ob dahinter ein Dienst steckt.
Für einen einzelnen Port ohne Installation meldet nc -zv server-ip 443 succeeded oder refused und hängt bei einem gefilterten Port bis zum Timeout. Für UDP hänge -sU an nmap an und rechne mit Wartezeit; auf unserem Server waren die drei üblichen UDP-Ports 53, 123 und 161 alle closed.
Ein Hinweis zu NAT-Tarifen: aus dem Internet ist nur der SSH-Port erreichbar, den wir an dich weitergeleitet haben. Ein Scan der geteilten Adresse zeigt unseren Rand, nicht deinen Server, und die übrigen Ports sind unerreichbar, egal was darauf lauscht. Bei einem Tarif mit eigener IPv4-Adresse verwaltest du jeden offenen Port selbst.
4. Schließen, was du nicht brauchst
Drei Wege, vom besten zum breitesten.
An localhost binden. Wenn nur Programme auf dem Server einen Dienst nutzen, lass ihn auf 127.0.0.1 lauschen. Wir starteten eine kleine Web-App mit --bind 127.0.0.1 8080: ss zeigte sie auf 127.0.0.1:8080, und nmap von außen meldete 8080/tcp closed. Für Datenbanken ist das die richtige Voreinstellung: Redis, MySQL und PostgreSQL lauschen unter Ubuntu ab Werk auf localhost, und unsere Anleitungen für Redis und MySQL lassen das so.
Den Dienst stoppen. sudo systemctl disable --now name entfernt den Listener dauerhaft. Prüfe mit ss, dass er weg ist.
Die Firewall einschalten. Erst SSH erlauben, dann das, was du anbietest, dann aktivieren:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose
Der Scan von außen änderte sich komplett:
Not shown: 997 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8081/tcp open blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds
Jeder nicht freigegebene Port wechselte von closed zu filtered, und der vollständige Scan aller 65.535 Ports dauerte nun 110 Sekunden statt 8,9. Mehr Zeit pro Port für den Scanner, weniger Informationen über deinen Server. Unsere UFW-Anleitung behandelt Rate-Limits, Freigaben für eine einzelne IP und IPv6.
Aber sieh dir die dritte Zeile an.
5. Die Docker-Überraschung
Port 8081 blieb bei aktiver Firewall und ohne erlaubende Regel offen. Der Port gehört zu einem Container, den wir mit docker run -p 8081:80 nginx:alpine gestartet hatten. Docker fügt der Kernel-Firewall eigene Regeln hinzu, und die werden vor denen von ufw ausgewertet. Die Regel, die es auf unserem Server schrieb:
-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80
Jedes Paket für Port 8081 wird in den Container weitergeleitet, bevor ufw mitreden kann. Das macht jeder Container mit -p oder einem ports:-Eintrag, und ss zeigt es ehrlich als docker-proxy auf 0.0.0.0:8081.
Die Lösung ist, nur auf localhost zu veröffentlichen:
docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine
In Compose schreibst du "127.0.0.1:8081:80" unter ports:. Nach der Änderung zeigte ss 127.0.0.1:8081, die Docker-Regel bekam -d 127.0.0.1/32, und der Scan von außen meldete 8081/tcp filtered. Stelle nginx als Reverse Proxy mit HTTPS davor, damit auf dem Server nur die Ports 22, 80 und 443 offen sind.
Checkliste nach jeder Änderung
sudo ss -tulpn: ist jede Zeile mit0.0.0.0oder[::]etwas, das du freigeben wolltest?nmap server-ipvom Laptop: stimmen die offenen Ports überein?- Eine Datenbank, ein Cache oder ein Admin-Panel auf einer öffentlichen Adresse: noch heute auf
127.0.0.1oder hinter die Firewall. - Jeder Docker-
ports:-Eintrag: beginnt er mit127.0.0.1:, wenn er nicht der öffentliche Web-Port ist? - SSH für alle offen: ergänze fail2ban oder Login nur per Schlüssel.
Unter Windows Server ist dieselbe Prüfung Get-NetTCPConnection -State Listen, und die Regeln stehen in der Windows-Firewall. Jeder Linux-Tarif ab dem Nano für $3 hat eine Variante mit eigener IPv4-Adresse, und dort sieht die Welt, was du in ss siehst, abzüglich deiner Firewall.
Kommentare
Noch keine Kommentare. Sei der Erste.