Hinter fast jeder dynamischen Webseite steckt ein leistungsstarkes Trio: Ein Webserver, eine Skriptsprache und eine Datenbank. In dieser Rubrik findest du Anleitungen und Tipps zu Nginx, PHP und MariaDB – den drei entscheidenden Bausteinen, um deine Anwendungen wie WordPress oder Nextcloud schnell, sicher und stabil auf einem eigenen VPS zu betreiben. Die Installationen habe ich auf Ubuntu durchgeführt.
Die Installation
Nginx – Installation
Als erstes beginnen wir mit Nginx. Für den Webserver nutze ich die Paketquellen direkt vom Hersteller. Um diese zu nutzen, müssen wir erstmal den Key hinzufügen und dann die Paketquellen holen:
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" | tee /etc/apt/sources.list.d/nginx.list
Code-Sprache: Bash (bash)
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
Code-Sprache: Bash (bash)
Nun aktualisieren wir die Paketquellen und installieren Nginx:
apt update && apt install nginx
Code-Sprache: Bash (bash)
PHP – Installation
Die reine Installation von PHP erledigen wir ganz einfach mit:
apt install php-fpm php-gd php-curl php-xml php-zip php-intl php-mbstring php-bz2 php-json php-apcu php-imagick php-gmp php-bcmath
Code-Sprache: Bash (bash)
Weitere Konfigurationen hängen auch ein wenig davon ab, welche Dienste wir später nutzen wollen. Ob Nextcloud oder WordPress, etc.
mariaDB (Datenbank) – Installation
Auch bei mariaDB nutzen wir die Pakete des Herstellers. Als erstes fügen wir hier auch wie bei Nginx den Key hinzu und anschließend die Paketquellen:
wget -O- https://mariadb.org/mariadb_release_signing_key.asc | gpg --dearmor | sudo tee /usr/share/keyrings/mariadb-keyring.gpg >/dev/null
Code-Sprache: Bash (bash)
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/mariadb-keyring.gpg] https://mirror.netcologne.de/mariadb/repo/11.4/ubuntu $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/mariadb.list
Code-Sprache: Bash (bash)
Danach aktualisieren wir einmal die Paketquellen und installieren mariaDB. Zusätzlich benötigen wir auch noch das PHP Paket dazu:
apt update && apt install mariadb-server php-mysql
Code-Sprache: Bash (bash)
Die Konfiguration
Nginx – Konfiguration
Als erstes beginnen wir mit der Konfiguration von Nginx. Wir erstellen eine Kopie der conf, für alle Fälle und dann öffnen wir die nginx.conf:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
Code-Sprache: Bash (bash)
nano /etc/nginx/nginx.conf
Code-Sprache: Bash (bash)
In der nginx.conf stellen wir nun sicher, dass folgende Zeilen vorhanden sind:
user www-data;
worker_processes auto;
server_tokens off;
Code-Sprache: Nginx (nginx)
user www-data;
Diese Anweisung legt fest, unter welchem Benutzerkonto Nginx seine Arbeitsprozesse ausführt. www-data ist der Standardbenutzer für Webserver unter Debian/Ubuntu und hat aus Sicherheitsgründen nur eingeschränkte Rechte. Das ist gut so, denn falls es eine Sicherheitslücke in WordPress oder einem Plugin geben sollte, kann der Angreifer nicht sofort das ganze System übernehmen.
worker_processes auto;
Hier wird Nginx angewiesen, die Anzahl der Arbeitsprozesse automatisch an die Anzahl der verfügbaren CPU-Kerne deines VPS anzupassen. Das sorgt für eine optimale Leistung, da Anfragen parallel auf den verschiedenen Kernen verarbeitet werden können, ohne dass wir manuell etwas einstellen müssen.
server_tokens off;
Dies ist eine reine Sicherheitsmaßnahme. Diese Anweisung verhindert, dass Nginx bei Fehlerseiten die genaue Versionsnummer deines Servers anzeigt. Das ist wichtig, um es potenziellen Angreifern zu erschweren, gezielt nach bekannten Schwachstellen in einer bestimmten Nginx-Version zu suchen.
Zuletzt, deaktivieren wir noch den Standard vHost der bei der Nginx Installation automatisch aktiv ist und starten danach Nginx neu:
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf_disabled
Code-Sprache: Bash (bash)
systemctl reload nginx
Code-Sprache: Bash (bash)
Wenn wir Nginx vom Hersteller installiert haben und nicht aus den Standardpaketen, werden die vHosts im Pfad /etc/nginx/conf.d/ abgelegt. Diese sind dann automatisch aktiv, wenn die Datei mit conf endet und Nginx dann neu gestartet wird. Wir benutzen hier auch den Befehl reload statt restart, da bei einem Restart der Webserver angehalten wird und es so zu einer kurzfristigen Unterbrechung kommt. Bei einem Reload, werden nur die Konfigurationsdateien neu eingelesen .
Wir erstellen unseren ersten vHost
Wir nutzen eine conf Datei die alle http Anfragen an alle Domains auf unserem Server auf Port 80 abfängt und weiter verarbeitet. Das bedeutet, dass alle Domains welche auf unsere IP Adressen (IPv4 und IPv6) zeigen, hier angenommen werden. Wir bereiten sie auch schon soweit vor, dass wir mit acme.sh SSL Zertifikate für unser Domains erstellen können für den späteren Zugriff mit https.
Die Anleitung wie ihr Zertifikate mit acme.sh erstellt, findet ihr >hier<.
Wir erstellen nun eine Standard conf für den Zugriff per http:
nano /etc/nginx/conf.d/http_default_gateway.conf
Diese füllen wir nun mit folgendem Inhalt:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Eintrag fuer die Zertifikats-Erstellung mit acme.sh
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt;
}
# Umleitung der Domains auf https
location / {
return 301 https://$host$request_uri;
}
}
Code-Sprache: PHP (php)
Nun laden wir Nginx noch einmal neu mit systemctl reload nginx .Jetzt ist unser Webserver per http erreichbar und leitet Anfragen per https an unsere Domain weiter. Dafür benötigen wir natürlich auch eine entsprechende vHost conf.
Anweisungen die für alle vHosts gleich sein sollen speichern wir in seperaten conf Dateien. Als erstes erstellen wir das Verzeichnis und dann die Datei für die Header Anweisungen:
mkdir -p /etc/nginx/snippets
nano /etc/nginx/snippets/headers.conf
Hier fügen wir nun folgenden Inhalt ein:
# HSTS (ngx_http_headers_module is required) In order to be recoginzed by SSL test, there must be an index.hmtl in the server's root
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload;" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Download-Options noopen always;
add_header X-Permitted-Cross-Domain-Policies none always;
add_header Referrer-Policy no-referrer always;
add_header X-Frame-Options "SAMEORIGIN" always;
# Disable FLoC
add_header Permissions-Policy "interest-cohort=()";
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
Code-Sprache: PHP (php)
Wir speichern und beenden nun mit Strg + x. Die ssl.conf welche wir noch benötigen, erstellen wir in der Anleitung für acme.sh, welche ihr >hier< findet.
Wenn wir nun ein Zertifikat erstellt haben und die headers.conf sowie die ssl.conf vorhanden sind, können wir unsere vHost für die SSL Verbindung zu unserem Webserver erstellen:
nano /etc/nginx/conf.d/meinedomain.de.conf
Diese füllen wir nun mit folgendem Inhalt. Denkt bitte daran, je nachdem welche Dienste ihr nutzt, müssen manche Zeilen angepasst werden.
map $arg_v $asset_immutable {
"" "";
default "immutable";
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name deinedomain.de www.deinedomain.de;
root /var/www/deinedomain.de;
index index.php index.html index.htm;
access_log /var/log/nginx/deinedomain.access.log;
error_log /var/log/nginx/deinedomain.error.log warn;
# SSL configuration
# RSA certificates
ssl_certificate /etc/letsencrypt/deinedomain.de/rsa/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/deinedomain.de/rsa/key.pem;
# ECC certificates
ssl_certificate /etc/letsencrypt/deinedomain.de/ecc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/deinedomain.de/ecc/key.pem;
# This should be ca.pem (certificate with the additional intermediate certificate)
# See here: https://certbot.eff.org/docs/using.html
# ECC
ssl_trusted_certificate /etc/letsencrypt/deinedomain.de/ecc/ca.pem;
# Include SSL configuration
include /etc/nginx/snippets/ssl.conf;
# Include headers
include /etc/nginx/snippets/headers.conf;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_types text/plain application/javascript application/json text/css;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass php-handler;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?)$ {
expires 365d;
access_log off;
add_header Cache-Control "public, max-age=31536000, $asset_immutable";
}
location ~* \.(?:html|xml|rss)$ {
expires -1;
add_header Cache-Control "no-cache";
}
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
# Verhindert den direkten Zugriff auf sensible Dateien
location ~* /(?:\.env|\.git|wp-config\.php)$ {
deny all;
}
}
Code-Sprache: PHP (php)
Nun prüfen wir einmal die Konfiguration auf Fehler mit nginx -t und wenn keine Fehler angezeigt werden, laden wir Nginx einmal neu mit systemctl reload nginx, um die neuen Konfigurationen zu aktivieren.
Diese Konfiguration könnte man beispielsweise für WordPress nutzen. Alle Aufrufe von deinedomain.de werden nun auf https umgeleitet und es wird eine verschlüsselte Verbindung aufgebaut.
PHP – Konfiguration
Bei PHP Konfiguration fangen wir mit der Datei www.conf an. Hier müssen wir darauf achten, dass wir auch die Version angeben, die wir auch wirklich installiert haben:
nano /etc/php/8.3/fpm/pool.d/www.conf
Code-Sprache: Bash (bash)
Als erstes kontrollieren wir, dass auch der richtige Nutzer angegeben ist, der später auch PHP nutzen wird:
user = www-data
group = www-data
Da die Kommunikation zwischen dem Webserver und PHP über einen Socket erfolgen soll, müssen wir noch den Pfad angeben:
listen = /run/php/php8.3-fpm.sock
Code-Sprache: JavaScript (javascript)
Manche Dienste, z.B. Nextcloud, benötigen noch einige Umgebungsvariablen. Wir kontrollieren ob die Zeilen aktiv sind oder auskommentiert und ob der Inhalt auch mit folgenden Zeilen übereinstimmt:
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
Code-Sprache: PHP (php)
Danach speichern wir die Datei und beenden den Editor mit Strg + x.
Weiter geht es nun mit der php.ini:
nano /etc/php/8.3/fpm/php.ini
Code-Sprache: Bash (bash)
Dort bearbeiten wir nun unter anderem das Memory Limit und aktivieren OPcache:
cgi.fix_pathinfo = 0
# Das Limit kann auch je nach Größe des Servers erhöht werden
memory_limit = 512M
# Cache aktivieren
opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 1
opcache.save_comments = 1
Code-Sprache: PHP (php)
Auch hier speichern und beenden wir dann mit Strg+x. Danach ist dann die php.ini für das cli Modul dran:
nano /etc/php/8.3/cli/php.ini
Code-Sprache: Bash (bash)
Hier kontrollieren wir ob folgende Einträge vorhanden sind. Eventuell sind sie auskommentiert oder noch nicht vorhanden:
cgi.fix_pathinfo = 0
apc.enable_cli = 1
Das war’s auch schon. Nun können wir auch hier wieder speichern und beenden mit Strg + x.
Danach starten wir den Dienst einmal neu, um die Anpassungen zu übernehmen:
systemctl restart php8.3-fpm
Code-Sprache: Bash (bash)
mariaDB (Datenbank ) – Konfiguration
Als erstes werden wir mariaDB absichern. Wir vergeben ein root Passwort und alle anderen Fragen beantworten wir mit ja (y):
mysql_secure_installation
Danach müssen wir noch die Konfiguration bearbeiten. Als erstes öffnen wir die Konfigurationsdatei:
nano /etc/mysql/mariadb.conf.d/50-server.cnf
Hier müssen wir dem Block [mysqld] noch zwei Zeilen hinzufügen. Sollte der Block noch nicht vorhanden sein, müssen wir ihn komplett hinzufügen:
[mysqld]
innodb_read_only_compressed=OFF
slave_connections_needed_for_purge=0
Kurz zur Erklärung:
innodb_read_only_compressed=OFF
Dieser Parameter steuert eine hochspezialisierte und veraltete Funktion, um komprimierte InnoDB-Tabellen von schreibgeschützten Medien (wie z.B. einer DVD) zu lesen. Für eine dynamische Webseite wie zum Beispiel WordPress, die permanenten Schreibzugriff auf die Datenbank benötigt, muss diese Funktion deaktiviert sein. Der Wert OFF ist hier also der korrekte und für den Betrieb notwendige Standard.
slave_connections_needed_for_purge=0
Hierbei handelt es sich um einen MariaDB-spezifischen Parameter, der nur in einem Setup mit Datenbank-Replikation (Master-Slave/Replica) eine Rolle spielt. Er steuert den sogenannten „Purge“-Thread, die interne „Müllabfuhr“ der Datenbank. Der Wert 0 weist MariaDB an, keine Verzögerung bei diesem Aufräumprozess anzuwenden. Für einen einzelnen, alleinstehenden Server, wie er für WordPress zum Beispiel typisch ist, ist dies der korrekte und empfohlene Standardwert.
Eigentlich sollten die Werte bei einer mariaDB Standard sein, aber wir gehen hier auf Nummer sicher.