nginx auf Debian und Ubuntu installieren und einrichten
Vom apt-Paket bis zum ersten Server-Block mit PHP-FPM und HTTPS: welche nginx-Version welche Distribution liefert, was beim nginx.org-Repo anders ist und wie man typische Fehler wieder loswird.
nginx zu installieren dauert dreißig Sekunden. Der Rest des Tages geht dafür drauf, dass der Server-Block nicht greift, PHP einen 502 wirft oder Apache bereits auf Port 80 sitzt. Dieser Artikel geht genau diese Stellen durch, und zwar getrennt nach Debian 13, Debian 12, Ubuntu 24.04 und Ubuntu 22.04, weil die vier Systeme sich an mehreren Punkten unterschiedlich verhalten.
Welches nginx: Distributionspaket oder offizielles Repository
Die erste Entscheidung fällt vor dem ersten Befehl. Die Distributionen liefern jeweils eine eingefrorene Version, die nur noch Sicherheitspatches bekommt. Stand Juli 2026 sieht das so aus:
| System | nginx aus dem Distributionspaket |
| Debian 13 (trixie) | 1.26.3 |
| Debian 12 (bookworm) | 1.22.1 |
| Ubuntu 24.04 LTS (noble) | 1.24.0 |
| Ubuntu 22.04 LTS (jammy) | 1.18.0 |
Bei nginx.org selbst stehen im Juli 2026 die Zweige 1.30.4 (stable) und 1.31.3 (mainline) bereit. Der Abstand zu Ubuntu 22.04 beträgt damit rund sechs Jahre Featureentwicklung.
Nimm das Distributionspaket, wenn du klassische Webseiten oder Reverse-Proxy-Setups fährst und unattended-upgrades die Arbeit machen sollen. Nimm das nginx.org-Repository, wenn du HTTP/3 und QUIC brauchst (in Ubuntu 22.04 gar nicht vorhanden, in Debian 12 ebenfalls nicht), wenn du fertige dynamische Module wie nginx-module-brotli oder das ACME-Modul willst, oder wenn du auf mehreren Distributionen exakt dieselbe Version fahren möchtest.
Was viele Anleitungen verschweigen: die beiden Pakete sind nicht dasselbe Programm in verschiedenen Versionen, sie sind unterschiedlich gebaut und unterschiedlich verpackt. Das ist der häufigste Grund, warum eine kopierte Anleitung nicht funktioniert.
| Merkmal | Debian/Ubuntu-Paket | nginx.org-Paket |
| Prozessbenutzer | www-data | nginx |
| Standard-Docroot | /var/www/html | /usr/share/nginx/html |
| sites-available und sites-enabled | vorhanden | gibt es nicht |
| /etc/nginx/snippets/ | vorhanden | gibt es nicht |
| ufw-Profile (Nginx Full etc.) | vorhanden | gibt es nicht |
| Dynamische Module | libnginx-mod-* | nginx-module-* |
Installation aus dem Distributionspaket
Auf allen vier Systemen identisch:
apt update
apt install -y nginx
apt install -y curl
Das Metapaket nginx genügt. nginx-full und nginx-extras existieren in Debian 12 und 13 weiterhin, ziehen aber nur zusätzliche Modulpakete nach. Einzelne Module holst du gezielt, etwa apt install libnginx-mod-http-headers-more-filter.
curl steht bewusst gleich dabei. Es ist keine Abhängigkeit von nginx und fehlt auf einem frisch aufgesetzten Debian oder Ubuntu, auch nachdem nginx sauber installiert wurde. Ohne diesen Schritt bricht der erste Nachweisbefehl unten mit curl: command not found ab, und dieselbe Falle trifft später den Host-Header-Test und den PHP-Test.
Jetzt der Teil, den die meisten Anleitungen auslassen: der Nachweis, dass es wirklich läuft. Ein apt install ohne Fehlermeldung beweist gar nichts.
nginx -v
systemctl is-enabled nginx
systemctl is-active nginx
curl -I http://127.0.0.1/
Erwartet werden enabled, active und ein HTTP/1.1 200 OK mit einem Server-Header, der nginx nennt (Debian 13 antwortet mit Server: nginx, Ubuntu 24.04 mit Server: nginx/1.24.0). Erst dann steht der Webserver. Wer wissen will, mit welchen Optionen das Paket gebaut wurde, nutzt nginx -V (großes V), dort steht auch, ob --with-http_v3_module dabei ist.
Wenn ufw aktiv ist, fehlt jetzt noch die Firewallregel. Vorher ein Blick auf das Paket selbst: auf Ubuntu Server ist ufw ab Werk installiert und lediglich inaktiv, auf Debian ist es überhaupt nicht vorhanden. Dort quittiert der erste Aufruf sonst mit ufw: command not found. Installiere es also mit, das Paket gibt es auf allen vier Systemen:
apt install -y ufw
ufw app list
ufw allow 'Nginx Full'
Die Profile liefert das Paket nginx-common aus dem Distributionspaket mit: Nginx Full, Nginx HTTP und Nginx HTTPS, auf Debian 13 zusätzlich Nginx QUIC. Aus dem nginx.org-Repository installiert, tauchen sie in ufw app list gar nicht erst auf (siehe Tabelle oben).
Das offizielle nginx-Repository einbinden
nginx.org unterstützt bookworm, trixie, jammy und noble. apt-key ist abgekündigt, der Schlüssel gehört in ein eigenes Keyring und wird per signed-by referenziert.
apt install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring
Auf Ubuntu heißt das letzte Paket ubuntu-keyring statt debian-archive-keyring. Danach:
mkdir -p /root/.gnupg && chmod 700 /root/.gnupg
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
Das mkdir in der ersten Zeile ist kein Ballast. Auf einem frisch aufgesetzten Server existiert /root/.gnupg noch nicht, und das gpg 2.4.7 aus Debian 13 legt das Verzeichnis in genau dieser Aufrufkombination nicht selbst an. Der Prüfbefehl bricht dann mit gpg: Fatal: /root/.gnupg: directory does not exist! ab, und zwar mitten in der Ausgabe, also bevor die Fingerprints erscheinen. Wer sie sehen will, muss das Verzeichnis vorher anlegen, alternativ genügt ein einmaliges gpg --list-keys.
Der letzte Befehl ist ebenfalls kein Zierrat, er ist der eigentliche Gegencheck. Er zeigt drei Schlüssel, das ist so gewollt und kein Grund abzubrechen: den aktuellen Signaturschlüssel 8540 A6F1 8833 A80E 9C16 53A4 2FD2 1310 B49F 6B46 (signing-key-2@nginx.com), dazu 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62 und 9E9B E90E ACBC DE69 FE9B 204C BCDC D8A3 8D88 A2B3. Stehen dort andere Fingerprints, hat der Download etwas anderes geliefert als erwartet, und du brichst hier ab.
Jetzt die Paketquelle. Achte auf das Pfadsegment nach /packages/, denn nginx.org pflegt für Debian und Ubuntu zwei getrennte Verzeichnisbäume. Unter /packages/debian/dists/ gibt es weder noble noch jammy, die Ubuntu-Pakete liegen ausschließlich unter /packages/ubuntu/. Diese Fassung setzt das Segment selbst und läuft deshalb auf beiden Familien unverändert:
OS=$(. /etc/os-release; echo $ID)
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$OS $(lsb_release -cs) nginx" | tee /etc/apt/sources.list.d/nginx.list
$ID ist auf Debian debian und auf Ubuntu ubuntu, damit passt der Pfad automatisch zur Distribution. Trägst du stattdessen fest packages/debian ein, weil du eine Debian-Anleitung auf einem Ubuntu abarbeitest, schreibt echo die Zeile klaglos und der Fehler kommt erst beim nächsten apt update:
Err: http://nginx.org/packages/debian noble Release
E: The repository 'http://nginx.org/packages/debian noble Release' does not have a Release file.
Auf Ubuntu 22.04 steht an der Stelle jammy statt noble, die Ursache ist dieselbe. Für den mainline-Zweig schiebst du mainline/ vor den Distributionsnamen. Damit apt das nginx.org-Paket auch tatsächlich dem Distributionspaket vorzieht, braucht es ein Pinning, sonst gewinnt je nach Versionsnummer das falsche:
printf 'Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n' | tee /etc/apt/preferences.d/99nginx
apt update
apt-cache policy nginx
apt install -y nginx
apt-cache policy nginx ist die Kontrolle vor der Installation: als Candidate muss die nginx.org-Version stehen (etwa 1.30.4-1~noble) und als Priorität die 900 aus der Pin-Datei. Steht dort weiter die Version der Distribution, greift das Pinning nicht und du installierst gleich das falsche Paket.
Umstieg von einer bestehenden Distributionsinstallation
Läuft bereits das Distributionspaket, scheitert das Upgrade. Die Meldung lautet sinngemäß:
dpkg: error processing archive /var/cache/apt/archives/nginx_1.30.4-1~bookworm_amd64.deb (--unpack):
trying to overwrite '/etc/nginx/mime.types', which is also in package nginx-common 1.22.1-9+deb12u9
Das nginx.org-Paket kennt kein nginx-common, deshalb kollidieren die Dateien. Der saubere Weg: erst die Konfiguration sichern, dann das Distributionspaket entfernen, dann neu installieren.
tar czf /root/nginx-config-backup.tar.gz /etc/nginx
systemctl stop nginx
apt purge -y nginx nginx-common
apt install -y nginx
Dass tar dabei tar: Removing leading '/' from member names ausgibt, ist normal und kein Fehler. Danach ist /etc/nginx/sites-available weg und deine alten vHosts liegen nur noch im Tarball. Kopiere sie nach /etc/nginx/conf.d/ und gib ihnen die Endung .conf, sonst werden sie nicht geladen. Achte dabei auf zwei Dinge: include snippets/fastcgi-php.conf; existiert im nginx.org-Paket nicht, und der Prozessbenutzer heißt jetzt nginx, was Dateirechte und PHP-FPM-Sockets betrifft.
sites-available und sites-enabled richtig verstehen
Das Zweiverzeichnismodell ist eine reine Debian-Erfindung, nginx selbst kennt es nicht. In /etc/nginx/sites-available/ liegen alle Konfigurationsdateien, in /etc/nginx/sites-enabled/ liegen Symlinks auf die aktiven davon. Geladen wird nur, was in nginx.conf per include steht. Prüfe das im Zweifel selbst:
grep include /etc/nginx/nginx.conf
Im Debian- und Ubuntu-Paket stehen dort zwei Zeilen, include /etc/nginx/conf.d/*.conf; und include /etc/nginx/sites-enabled/*;. Im nginx.org-Paket steht nur die erste. Genau daher kommt der Klassiker: jemand folgt einer Ubuntu-Anleitung auf einem nginx.org-Paket, legt /etc/nginx/sites-available/meine-seite an, setzt den Symlink, bekommt bei nginx -t ein sauberes syntax is ok, und trotzdem passiert nichts. Es gibt keine Fehlermeldung, weil das Verzeichnis schlicht niemanden interessiert.
Der Gegentest, der immer die Wahrheit sagt, ist nginx -T mit großem T. Er gibt die vollständige aufgelöste Konfiguration aus, also exakt das, was nginx wirklich sieht:
nginx -T | grep -n "server_name\|listen\|root"
Taucht dein server_name dort nicht auf, wird die Datei nicht eingelesen. Punkt. Weiteres Debugging an der Konfiguration selbst ist dann Zeitverschwendung.
Der zweite häufige Fehler ist ein Symlink ins Leere, etwa nach einem Tippfehler oder weil die Zieldatei umbenannt wurde:
nginx: [emerg] open() "/etc/nginx/sites-enabled/meine-seite" failed (2: No such file or directory) in /etc/nginx/nginx.conf:62
Kaputte Symlinks findest du mit find /etc/nginx/sites-enabled/ -xtype l. Deaktiviert wird eine Seite übrigens nie durch Löschen in sites-available, sondern durch Entfernen des Symlinks mit unlink /etc/nginx/sites-enabled/meine-seite.
Der erste eigene Server-Block
Wir legen eine statische Seite an. Zuerst das Verzeichnis und eine Testdatei:
mkdir -p /var/www/beispiel/html
echo '<h1>KernelHost Testseite</h1>' > /var/www/beispiel/html/index.html
chown -R www-data:www-data /var/www/beispiel
Beim nginx.org-Paket lautet der Besitzer nginx:nginx. Jetzt die Konfiguration, und zwar wirklich als eigener Schritt: die Datei muss existieren, bevor der Symlink auf sie zeigt. Leg sie mit dem Editor an oder schreib sie direkt per Heredoc, im nginx.org-Paket geht sie stattdessen nach /etc/nginx/conf.d/beispiel.conf:
cat > /etc/nginx/sites-available/beispiel <<'EOF'
server {
listen 80;
listen [::]:80;
server_name beispiel.at www.beispiel.at;
root /var/www/beispiel/html;
index index.html;
access_log /var/log/nginx/beispiel.access.log;
error_log /var/log/nginx/beispiel.error.log;
location / {
try_files $uri $uri/ =404;
}
}
EOF
Die einfachen Anführungszeichen um 'EOF' sind wichtig, sonst ersetzt die Shell $uri durch nichts und der Block ist beim Speichern schon kaputt. Erst danach aktivieren, prüfen, neu laden:
ln -s /etc/nginx/sites-available/beispiel /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
Die Reihenfolge ist nicht beliebig. ln legt den Symlink auch dann an, wenn die Zieldatei gar nicht existiert, ohne ein Wort zu sagen. Auffliegen tut das erst im Test:
nginx: [emerg] open() "/etc/nginx/sites-enabled/beispiel" failed (2: No such file or directory) in /etc/nginx/nginx.conf:61
Die Zeilennummer unterscheidet sich je nach System (61 auf Debian 13, 60 auf Debian 12 und den beiden Ubuntu), die Meldung ist identisch. Ein reload würde in diesem Zustand abgelehnt, die laufende Konfiguration bleibt also unangetastet. Genau diesen Fall findet auch find /etc/nginx/sites-enabled/ -xtype l.
Drei weitere Fehler tauchen an dieser Stelle immer wieder auf.
Doppelter Default-Server. Wer default_server aus einer Anleitung mitkopiert, obwohl die Debian-Standardseite noch aktiv ist, bekommt:
nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/beispiel:2
Entweder das Schlüsselwort weglassen oder die Standardseite abschalten mit unlink /etc/nginx/sites-enabled/default.
Doppelter Servername. Steht derselbe Name in zwei Blöcken, gewinnt kommentarlos der zuerst geladene, es gibt nur eine Warnung:
nginx: [warn] conflicting server name "beispiel.at" on 0.0.0.0:80, ignored
Zu langer Servername. Bei langen Domains oder vielen Subdomains:
nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 32
Abhilfe ist server_names_hash_bucket_size 64; im http-Block von nginx.conf.
Der Funktionsnachweis läuft ohne DNS über den Host-Header:
curl -H 'Host: beispiel.at' -sS http://127.0.0.1/
Kommt die Debian-Standardseite statt deiner Testseite, greift dein Block nicht und du landest im Default-Server. Kommt deine Seite, passt es.
PHP-FPM einbinden
Hier lauert die unangenehmste Falle der ganzen Anleitung, und sie kostet reihenweise Leute eine Stunde. Das Metapaket php hängt über php8.x an der Alternative libapache2-mod-php8.x | php8.x-fpm | php8.x-cgi. apt wählt immer die erste, also installiert apt install php zuverlässig Apache mit, und Apache belegt danach Port 80. Installiere deshalb gezielt:
apt install -y php-fpm php-mysql php-xml php-curl php-mbstring php-zip
Welche PHP-Version du bekommst und wie der Socket heißt, hängt an der Distribution:
| System | PHP | Socket | Dienst |
| Debian 13 | 8.4 | /run/php/php8.4-fpm.sock | php8.4-fpm |
| Debian 12 | 8.2 | /run/php/php8.2-fpm.sock | php8.2-fpm |
| Ubuntu 24.04 | 8.3 | /run/php/php8.3-fpm.sock | php8.3-fpm |
| Ubuntu 22.04 | 8.1 | /run/php/php8.1-fpm.sock | php8.1-fpm |
Rate den Pfad nicht, lies ihn ab:
ls -l /run/php/
Der PHP-Block im Server (Beispiel Debian 12, Pfad anpassen):
index index.php index.html;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.(?!well-known).* {
deny all;
}
Auf Debian und Ubuntu kannst du die ersten Zeilen durch include snippets/fastcgi-php.conf; ersetzen, im nginx.org-Paket gibt es dieses Snippet nicht, dort brauchst du die ausgeschriebene Fassung. Das try_files $uri =404; ist kein Stilmittel, es verhindert, dass hochgeladene Dateien mit angehängtem .php im Pfad ausgeführt werden.
Ein Schritt fehlt aber noch, und er wird fast immer übersprungen. Der Testaufruf gleich läuft über den mitgelieferten Default-Vhost, und in /etc/nginx/sites-available/default ist der Abschnitt location ~ \.php$ ab Werk vollständig auskommentiert. Solange das so bleibt, reicht nginx .php-Dateien unverändert durch: du bekommst einen 200 OK mit Content-Type: application/octet-stream und den PHP-Quelltext im Klartext. Das ist kein Schönheitsfehler. In einer echten Datei stehen an dieser Stelle Datenbankzugangsdaten oder API-Schlüssel, und die liest dann jeder mit, der die URL kennt.
Also zuerst den Block scharf schalten. Entferne in /etc/nginx/sites-available/default die Kommentarzeichen davor oder trag ihn ausgeschrieben ein:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
Der Socketname richtet sich nach der Distribution, die Tabelle oben nennt ihn: php8.4 auf Debian 13, php8.3 auf Ubuntu 24.04, php8.2 auf Debian 12, php8.1 auf Ubuntu 22.04. Danach prüfen und neu laden:
nginx -t
systemctl reload nginx
Erst jetzt hat der Nachweis Aussagekraft, dass PHP wirklich über FPM läuft und nicht nur der Download der Datei angeboten wird:
printf '<?php echo "PHP ", PHP_VERSION, " via ", php_sapi_name(), "\n";' > /var/www/html/kh-check.php
curl -s http://127.0.0.1/kh-check.php
rm /var/www/html/kh-check.php
Richtig ist eine Ausgabe wie PHP 8.4.23 via fpm-fcgi, auf Ubuntu 24.04 entsprechend PHP 8.3.6 via fpm-fcgi. Kommt stattdessen der Quelltext zurück, greift location ~ \.php$ im aktiven Vhost noch nicht, ist also weiterhin auskommentiert oder zeigt auf den falschen Socket. Löschen nicht vergessen, und benutze kein phpinfo() auf einem erreichbaren Server.
502 Bad Gateway richtig lesen
Ein 502 ist keine Diagnose, die Diagnose steht in /var/log/nginx/error.log. Es gibt drei typische Zeilen:
connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory) while connecting to upstream
Falscher Pfad oder FPM läuft nicht. Prüfe mit systemctl status php8.2-fpm und ls /run/php/. Ein häufiger Auslöser ist ein Distributionsupgrade: nach dem Sprung von Debian 12 auf 13 zeigt die alte Konfiguration noch auf php8.2-fpm.sock, installiert ist aber 8.4.
connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) while connecting to upstream
Das trifft fast ausschließlich Installationen aus dem nginx.org-Repository. Der FPM-Pool gehört www-data und hat Modus 0660, nginx läuft dort aber als Benutzer nginx. Setze in /etc/php/8.2/fpm/pool.d/www.conf den Wert listen.group = nginx und starte FPM neu.
FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream
SCRIPT_FILENAME zeigt ins Leere. Meist steht root im location-Block statt im server-Block, oder es fehlt ganz.
HTTPS mit Let's Encrypt
Die certbot-Versionen der Distributionen liegen weit auseinander: Debian 13 liefert 4.0.0, Ubuntu 24.04 die 2.9.0, Debian 12 die 2.1.0 und Ubuntu 22.04 nur 1.21.0. Alle sprechen ACMEv2, für neuere Funktionen wie kurzlebige Zertifikate ist die 1.21 aber zu alt. Auf Ubuntu 22.04 lohnt der Umweg über snap.
apt install -y certbot python3-certbot-nginx
certbot --version
Vor dem Ausstellen müssen drei Bedingungen erfüllt sein, sonst scheitert die Validierung ohne brauchbare Meldung: der A-Record (und gegebenenfalls AAAA) zeigt auf den Server, Port 80 ist von außen erreichbar, und es existiert ein Server-Block mit passendem server_name. Der letzte Punkt ist entscheidend, denn das nginx-Plugin findet den Block über server_name, nicht über den Dateinamen. Danach:
certbot --nginx -d beispiel.at -d www.beispiel.at
certbot schreibt daraufhin in deine Konfigurationsdatei: einen zweiten Server-Block mit listen 443 ssl, die Pfade zu fullchain.pem und privkey.pem, ein include /etc/letsencrypt/options-ssl-nginx.conf und eine Weiterleitung von Port 80. Dass die Datei verändert wird, überrascht regelmäßig. Wer sie danach von Hand überschreibt, verliert das HTTPS und bekommt beim nächsten Reload:
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/beispiel.at/fullchain.pem": BIO_new_file() failed
Die automatische Erneuerung läuft über einen systemd-Timer, nicht über einen Cronjob. Kontrolliere beides:
systemctl list-timers | grep certbot
certbot renew --dry-run
Der Trockenlauf ist der einzige echte Beweis, dass die Erneuerung in neunzig Tagen klappt. Wenn du das nginx.org-Repository nutzt, gibt es seit nginx 1.29 mit nginx-module-acme außerdem eine Variante ganz ohne certbot, bei der nginx die Zertifikate selbst anfordert und erneuert.
nginx -t, reload und restart
Die Regel ist simpel und wird trotzdem ständig verletzt: niemals reload ohne vorheriges nginx -t. Bei einem Syntaxfehler weigert sich nginx zwar, die kaputte Konfiguration zu übernehmen, aber bei einem restart ist der alte Prozess bereits beendet und die Seite ist offline.
nginx -t
Erwartet wird genau das:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
reload startet neue Worker mit der neuen Konfiguration und lässt die alten ihre offenen Verbindungen zu Ende bedienen. Keine einzige Anfrage geht verloren. restart brauchst du nur, wenn sich etwas am Prozess selbst ändert, etwa nach einem Paketupdate, bei geändertem user oder beim Nachladen von load_module-Direktiven.
Wenn der Start scheitert, ist die Ausgabe von systemd fast nutzlos:
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.
Der brauchbare Text steht eine Ebene tiefer:
journalctl -xeu nginx.service --no-pager -n 30
Portkonflikt mit Apache auf Port 80 lösen
Der bekannteste nginx-Fehler überhaupt:
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Beachte, dass nginx -t diesen Fehler nicht findet, der Test bindet keine Ports. Erst der Start scheitert. Finde zuerst heraus, wer den Port hält, statt zu raten:
apt install -y iproute2
ss -tlnp | grep ':80'
Die Ausgabe nennt den Prozess im Klartext, typischerweise users:(("apache2",pid=612,fd=4)). In neun von zehn Fällen kam Apache über apt install php oder über ein Panel mit ins System. Es gibt drei saubere Auswege.
Erstens, Apache abschalten. Wenn du ihn nicht brauchst, reicht das Deaktivieren, damit er nach dem nächsten Neustart nicht zurückkommt:
systemctl disable --now apache2
systemctl start nginx
Zweitens, Apache entfernen. Achtung: hängt libapache2-mod-php daran, nimmt apt purge apache2 unter Umständen PHP mit. Prüfe die Liste, die apt vor der Ausführung anzeigt, und installiere danach php-fpm nach.
Drittens, beide parallel betreiben. Das ist sinnvoll, wenn bestehende Apache-vHosts mit .htaccess weiterlaufen sollen und nginx davor als Reverse Proxy arbeitet. Dazu setzt du in /etc/apache2/ports.conf ein Listen 127.0.0.1:8080, änderst in jedem vHost <VirtualHost *:80> auf <VirtualHost *:8080> und lässt nginx weiterleiten:
location / {
proxy_pass http://127.0.0.1:8080;
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 $scheme;
}
Damit Apache in seinen Logs nicht mehr nur 127.0.0.1 sieht, aktivierst du dort das Modul remoteip. Ohne diesen Schritt sind Fail2ban-Regeln auf Apache-Logs wertlos, weil jede Anfrage von localhost zu kommen scheint.
Zwei Varianten desselben Fehlers werden gern übersehen. Steht in zwei deiner eigenen Dateien derselbe listen-Eintrag doppelt, meldet nginx ebenfalls Address already in use, obwohl gar kein Apache im Spiel ist. Und bei bind() to [::]:80 failed ist nur IPv6 betroffen, meist weil ein zweiter Block ebenfalls listen [::]:80 ohne ipv6only=on setzt.
Die Abnahme: acht Prüfungen statt Bauchgefühl
Bevor du eine Installation als fertig betrachtest, laufen diese Punkte durch. Jeder einzelne hat schon einmal einen stillen Ausfall verhindert.
nginx -tmeldet test is successful.systemctl is-enabled nginxgibtenabledzurück, der Dienst kommt nach einem Reboot also wieder.ss -tlnp | grep nginxzeigt Port 80 und, falls eingerichtet, Port 443.nginx -T | grep server_namelistet jede Domain, die laufen soll.curl -I http://127.0.0.1/liefert einen 200er oder eine beabsichtigte Weiterleitung.- Die PHP-Testdatei gibt
fpm-fcgiaus und wurde danach gelöscht. certbot renew --dry-runläuft fehlerfrei durch./var/log/nginx/error.logenthält nach einem Testzugriff keine neuen Einträge.
Auf einem frisch aufgesetzten Server gehört danach noch die Firewall dazu, siehe dazu unsere Anleitung zur Einrichtung von ufw. Debian 10 und Ubuntu 20.04 sind seit Juni 2024 beziehungsweise Mai 2025 ohne Support und bekommen keine nginx-Sicherheitsupdates mehr, ein Upgrade ist dort kein Komfortthema, sondern überfällig.
Was bei KernelHost dazukommt
Auf den KVM-Rootservern und Dedicated Servern von KernelHost installierst du Debian 13, Debian 12, Ubuntu 24.04 oder Ubuntu 22.04 direkt aus dem Kundenbereich und hast danach vollen Root-Zugriff, die oben beschriebenen Schritte laufen unverändert. Die Systeme stehen im Rechenzentrum maincubes in Frankfurt am Main, TÜV-zertifiziert nach TIER3+, angebunden über unser eigenes Netz. Der DDoS-Schutz greift dabei vor dem Server und nicht erst in nginx: 3,2 Tbps Arbor-Echtzeitfilterung direkt am Standort, in den Professional-Tarifen zusätzlich bis zu 17 Tbps globale Filterkapazität. Alles läuft auf PrePaid-Basis, also ohne Mindestlaufzeit, ohne Kündigungsfrist und ohne Einrichtungsgebühr.
Häufige Fragen
Sollte ich nginx aus dem Distributionspaket oder aus dem offiziellen nginx.org-Repository installieren?
Warum wird mein Server-Block in sites-available komplett ignoriert, obwohl nginx -t erfolgreich ist?
nginx startet nicht und meldet bind() to 0.0.0.0:80 failed (98: Address already in use). Was tun?
Welche PHP-Version und welchen FPM-Socket brauche ich auf meiner Distribution?
Wie erkenne ich zuverlässig, dass nginx wirklich richtig läuft und nicht nur der Befehl durchgelaufen ist?
2026 KernelHost GmbH. Alle Rechte vorbehalten. Diese Anleitung ist urheberrechtlich geschützt. Eine Veröffentlichung auf anderen Webseiten, auch auszugsweise oder in bearbeiteter Form, ist ohne unsere schriftliche Zustimmung nicht gestattet. Zitate mit Quellenangabe und Link sind ausdrücklich willkommen.

