Nginx, PHP und MariaDB

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.listCode-Sprache: Bash (bash)
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/nullCode-Sprache: Bash (bash)

Nun aktualisieren wir die Paketquellen und installieren Nginx:

apt update && apt install nginxCode-Sprache: Bash (bash)

nach oben


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-bcmathCode-Sprache: Bash (bash)

Weitere Konfigurationen hängen auch ein wenig davon ab, welche Dienste wir später nutzen wollen. Ob Nextcloud oder WordPress, etc.

nach oben


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/nullCode-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.listCode-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-mysqlCode-Sprache: Bash (bash)

nach oben


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.bakCode-Sprache: Bash (bash)
nano /etc/nginx/nginx.confCode-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_disabledCode-Sprache: Bash (bash)
systemctl reload nginxCode-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 .

nach oben


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.

nach oben


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.confCode-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.sockCode-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] = /tmpCode-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.iniCode-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 = 1Code-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.iniCode-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-fpmCode-Sprache: Bash (bash)

nach oben


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.

nach oben