---
id: 2024-10-13-ubuntu-24-04-lemp-stack-installieren
slug: ubuntu-24-04-lemp-stack-installieren
title: "LEMP-Stack auf Ubuntu 24.04 LTS einrichten: Nginx, MariaDB, PHP 8.3 & Let’s Encrypt"
excerpt: "Produktionsreife Installation des LEMP-Stacks auf Ubuntu 24.04 LTS: Nginx Webserver, MariaDB 10.11, PHP 8.3-FPM Pool-Tuning, UFW-Absicherung und automatisches Let’s Encrypt TLS."
date: "2024-10-13T09:00:00+02:00"
updated: "2024-10-13T09:00:00+02:00"
author:
  name: "Sebastian Palencsár"
  handle: "spalencsar"
category: "serverumgebungen"
tags: ["lempstack", "ubuntu2404", "nginx", "mariadb", "php8", "linuxadmin", "security"]
reading_time: 25
toc: true
---

<blockquote class="infobox infobox--info">
💡 **Aktuellere LTS-Generation verfügbar:** Planst du eine Neuinstallation auf der aktuellen Langzeitversion? Im Leitfaden [LEMP-Stack auf Ubuntu 26.04 LTS einrichten](/de/serverumgebungen/ubuntu-26-04-lemp-stack-installieren){.badge-link-text} findest du die Dokumentation für Nginx mit nativem HTTP/3 & QUIC, MariaDB 11.4 und PHP 8.5.
</blockquote>

Der LEMP-Stack repräsentiert eine der bewährtesten und ressourceneffizientesten Architekturen für den Betrieb dynamischer Webanwendungen unter Linux. [Linux](/de/tag/linux){.badge-link-text} bildet das stabile Betriebssystemfundament, [Nginx](https://www.nginx.com/){.badge-link-text} fungiert als hochperformanter, asynchroner Webserver und Reverse Proxy, [MariaDB](https://mariadb.org/){.badge-link-text} stellt die relationale Datenbankverwaltung bereit und [PHP](https://www.php.net/){.badge-link-text} führt die serverseitige Anwendungslogik aus.

Im Vergleich zu klassischen LAMP-Architekturen (Apache mit `mod_php`) zeichnet sich der LEMP-Stack durch strikte Prozessentkopplung aus. Nginx verarbeitet statische Anfragen über eine ereignisgesteuerte Non-Blocking-Architektur und delegiert dynamische Skriptaufrufe über Unix Domain Sockets an den FastCGI Process Manager (`php-fpm`). Das senkt den Arbeitsspeicherverbrauch bei parallelen Verbindungen drastisch und schützt das Gesamtsystem vor Lastspitzen.

Dieser Leitfaden führt dich durch den Aufbau eines gehärteten, produktionsbereiten LEMP-Stacks auf Basis von **Ubuntu 24.04 LTS (Noble Numbat)**. Wir behandeln die Systemhärtung mit UFW und Fail2ban, das Leistungs-Tuning von MariaDB und PHP 8.3-FPM sowie die verschlüsselte Auslieferung über Let’s Encrypt mit modernem TLS 1.3.

<blockquote class="infobox infobox--info">
💡 **Langzeitsupport (LTS):** Ubuntu 24.04 LTS garantiert offizielle Sicherheitsaktualisierungen und gepflegte Paketstände bis April 2029. In Produktionsumgebungen sorgt dieser planbare Fünfjahreszyklus für hohe Betriebsstabilität bei minimalem Wartungsaufwand.
</blockquote>

<blockquote class="infobox infobox--warn">
⚠️ **Voraussetzungen:** Erforderlich sind administrativer Zugriff (`sudo`) auf eine saubere Installation von Ubuntu 24.04 LTS, grundlegende Kenntnisse der Shell-Administration sowie ein öffentlich auflösbarer DNS-Record (A/AAAA) für deine Domain, falls ein Let's Encrypt TLS-Zertifikat ausgestellt werden soll.
</blockquote>

## Systemvoraussetzungen und Komponentenmatrix

Bevor Pakete installiert werden, sollte die Serverhardware auf den geplanten Workload abgestimmt sein. Nginx ist extrem genügsam, während relationale Datenbankabfragen und parallele PHP-Worker direkten Einfluss auf RAM und I/O-Performance haben.

| Komponente | Paketversion in Ubuntu 24.04 | Standard-Port / Socket | Mindestanforderung | Empfohlen für Produktion |
| --- | --- | --- | --- | --- |
| **Betriebssystem** | Ubuntu 24.04 LTS | - | 1 vCPU, 1 GB RAM | 2–4 vCPUs, 4–8 GB RAM |
| **Nginx** | 1.24+ | TCP 80, 443 | 128 MB RAM | 512 MB RAM |
| **MariaDB** | 10.11 LTS | TCP 3306 (127.0.0.1) | 512 MB RAM | 2 bis 4 GB RAM (InnoDB Buffer) |
| **PHP-FPM** | 8.3 | `/run/php/php8.3-fpm.sock` | 256 MB RAM | 1 bis 2 GB RAM (für Worker) |
| **Storage** | NVMe / SSD | - | 10 GB frei | 25+ GB mit dediziertem Backup |

## 1. Systemvorbereitung und Basisabsicherung

Jede saubere Serverbereitstellung beginnt mit einem aktualisierten Paketindex und einer restriktiven Firewall-Konfiguration. Dadurch wird verhindert, dass während der Einrichtung ungeschützte Standard-Dienste direkt aus dem Internet erreichbar sind.

### Paketquellen aktualisieren und Bereinigung durchführen

Zunächst bringen wir den lokalen Paketcache auf den aktuellen Stand und installieren anstehende Kernel- und Sicherheitsupdates:

```bash
# Paketlisten aktualisieren und installierte Pakete upgraden
sudo apt update && sudo apt upgrade -y

# Nicht mehr benötigte Alt-Abhängigkeiten restlos entfernen
sudo apt autoremove --purge -y && sudo apt autoclean
```

**Diagnose und Ressourcen-Check:**

Vor der Installation größerer Serverdienste verifizieren wir verfügbaren Speicherplatz, Arbeitsspeicher und Systemzeit:

```bash
# Festplattenplatz auf der Root-Partition prüfen
df -h /

# RAM-Verfügbarkeit und Swap-Status kontrollieren
free -h

# Zeitsynchronisation via NTP sicherstellen
timedatectl status
```

### Firewall (UFW) und Brute-Force-Schutz (Fail2ban)

Die Uncomplicated Firewall (UFW) verwaltet das Kernel-Paketfiltersystem (nftables) mit klaren Standardrichtlinien: Sämtlicher eingehender Traffic wird blockiert, ausgehende Verbindungen werden erlaubt.

```bash
# UFW und Fail2ban installieren
sudo apt install ufw fail2ban -y

# Standardrichtlinien definieren
sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH zwingend vor Aktivierung freischalten (Port 22)
sudo ufw allow OpenSSH

# Firewall aktivieren und Status prüfen
sudo ufw --force enable
sudo ufw status verbose
```

<blockquote class="infobox infobox--warn">
⚠️ **Kritische Stolperfalle:** Aktiviere UFW niemals, ohne zuvor den SSH-Dienst (`sudo ufw allow OpenSSH` oder `sudo ufw allow 22/tcp`) explizit freigegeben zu haben. Andernfalls verlierst du mit dem Aktivierungsbefehl sofort den Remote-Zugriff und benötigst die Notfallkonsole deines Hosters.
</blockquote>

```bash
# Bestehende Webserver-Dienste prüfen (z. B. Apache-Altlasten)
sudo ss -tuln | grep -E ':(80|443)' || echo "Ports 80 und 443 sind frei"
```

## 2. Nginx als Webserver installieren

Nginx fungiert als Frontend unseres Stacks. Er nimmt eingehende TCP-Verbindungen entgegen, terminiert TLS-Verschlüsselung, liefert statische Assets (CSS, JavaScript, Bilder) direkt aus und reicht dynamische PHP-Anfragen an den FPM-Socket weiter.

### Nginx installieren und Firewall-Regel erweitern

```bash
# Nginx aus den offiziellen Ubuntu-Repositories installieren
sudo apt install nginx -y

# Dienst für automatischen Systemstart registrieren und starten
sudo systemctl enable nginx
sudo systemctl start nginx

# HTTP (Port 80) und HTTPS (Port 443) in der Firewall freigeben
sudo ufw allow 'Nginx Full'
sudo ufw status
```

**Betriebszustand verifizieren:**

```bash
# Laufenden Status der Systemd-Unit prüfen
systemctl status nginx --no-pager

# Lokale HTTP-Antwort testen
curl -I http://127.0.0.1
```

Die Antwort muss den Statuscode `HTTP/1.1 200 OK` zusammen mit dem Header `Server: nginx/...` zurückgeben.

<blockquote class="infobox infobox--warn">
⚠️ **Portkonflikte vermeiden:** Startet Nginx nach der Installation nicht, blockiert häufig ein vorinstallierter Apache-Dienst (`apache2`) den Port 80. Prüfe mit `sudo ss -tuln | grep :80`. Falls Apache aktiv ist, deaktiviere ihn mit `sudo systemctl stop apache2 && sudo systemctl disable apache2`.
</blockquote>

### Verzeichnisstruktur von Nginx

Unter Debian und Ubuntu folgt Nginx einem modularen Aufbau, der Konfigurationsvorlagen (`sites-available`) von aktiven Instanzen (`sites-enabled`) über symbolische Links trennt:

```markdown
┌─────────────────────────────────────────────────────────────┐
│                 Nginx Verzeichnisstruktur                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   /etc/nginx/                                               │
│   ├── nginx.conf          Globale Hauptkonfiguration        │
│   ├── conf.d/             Modulare Server-Konfigurationen   │
│   ├── sites-available/    VHost-Vorlagen (Inaktiv)          │
│   └── sites-enabled/      Aktive VHost-Symlinks             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

* **`nginx.conf`:** Globale Parameter wie Worker-Prozesse, Event-Modelle, Gzip-Komprimierung und Logging-Formate.
* **`sites-available/`:** Konfigurationsdateien für individuelle virtuelle Hosts (Domains).
* **`sites-enabled/`:** Enthält Symlinks auf Dateien in `sites-available/`. Nur VHosts in diesem Ordner werden aktiv von Nginx geladen.
* **`conf.d/`:** Globale modulare Konfigurations-Snippets für Upstream-Pools oder Sicherheitsrichtlinien.

## 3. MariaDB Datenbankserver bereitstellen

Als relationales Datenbanksystem setzen wir auf MariaDB 10.11 LTS. MariaDB bietet volle Kompatibilität zu MySQL, zeichnet sich jedoch durch die moderne InnoDB-Speicher-Engine, verbesserte Thread-Pools und hohe Performance bei Web-Workloads aus.

### Installation und automatischer Start

```bash
# MariaDB Server und Client-Tools installieren
sudo apt install mariadb-server mariadb-client -y

# Dienst aktivieren und Status überprüfen
sudo systemctl enable mariadb
sudo systemctl status mariadb --no-pager
```

### Absicherung mit `mariadb-secure-installation`

Direkt nach der Installation enthält die Datenbank unsichere Standardeinstellungen (anonyme Benutzerkonten, remote zugänglicher Root-Account und eine öffentliche Testdatenbank). Wir bereinigen diese Schwachstellen:

```bash
# Interaktiven Sicherheitsassistenten ausführen
sudo mariadb-secure-installation
```

**Empfohlene Antworten für den Produktionsbetrieb:**

1. **Enter current password for root:** `Enter` drücken (Standardmäßig ist kein Kennwort gesetzt).
2. **Switch to unix_socket authentication [Y/n]:** `Y` (erlaubt Root-Login via System-Root ohne Klartextpasswort).
3. **Change the root password? [Y/n]:** `Y` (Vergib ein komplexes, langes Kennwort und sichere es extern).
4. **Remove anonymous users? [Y/n]:** `Y` (Anonyme Accounts restlos entfernen).
5. **Disallow root login remotely? [Y/n]:** `Y` (Root-Zugriff strikt auf `localhost` beschränken).
6. **Remove test database and access to it? [Y/n]:** `Y` (Test-Tabellen löschen).
7. **Reload privilege tables now? [Y/n]:** `Y` (Rechte-Tabellen sofort neu einlesen).

<blockquote class="infobox infobox--info">
💡 **Socket-Authentifizierung unter Linux:** Ubuntu nutzt für den MariaDB-Root-Account standardmäßig das Plugin `unix_socket`. Das bedeutet: Der System-Benutzer `root` kann sich per `sudo mariadb` direkt ohne Passwort anmelden. Ein externes Root-Passwort wird primär benötigt, wenn Drittanbieter-Tools ohne Sudo-Rechte lokalen administrativen Zugriff anfordern.
</blockquote>

### MariaDB Verzeichnisstruktur und Tuning

Die Konfiguration ist modular über das Verzeichnis `/etc/mysql/` organisiert:

```markdown
┌─────────────────────────────────────────────────────────────┐
│                MariaDB Verzeichnisstruktur                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   /etc/mysql/                                               │
│   ├── my.cnf              Zentraler Inkludierungs-Symlink   │
│   └── mariadb.conf.d/     Server-Konfigurationen            │
│       ├── 50-server.cnf   Hauptkonfiguration des Daemon     │
│       └── 60-galera.cnf   Cluster- & Replikationsprofile    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

Zur Optimierung für produktive Web-Server passen wir in `/etc/mysql/mariadb.conf.d/50-server.cnf` die InnoDB-Ressourcenzuteilung an:

```bash
# Konfiguration zur Bearbeitung öffnen
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```

**Kernparameter im Abschnitt `[mysqld]`:**

```ini
[mysqld]
# Netzwerkbindung strikt auf localhost beschränken
bind-address            = 127.0.0.1

# Performance und InnoDB Tuning
innodb_buffer_pool_size = 512M          # Bei 2 GB RAM; bei 4 GB+ auf 50-70% des RAMs setzen
innodb_log_file_size    = 128M          # Schreibt Transaktionen effizient ins Log
innodb_flush_log_at_trx_commit = 2     # Reduziert Disk-I/O bei Schreiblasten
innodb_file_per_table   = 1             # Erstellt eigene .ibd-Dateien pro Tabelle
max_connections         = 100           # Verhindert Überlastung durch zu viele Clients
```

**Erläuterung der Tuning-Parameter:**

| Parameter | Empfohlener Wert | Wirkung im Serverbetrieb |
| --- | --- | --- |
| `bind-address` | `127.0.0.1` | Schützt MariaDB vor direktem Netzwerkzugriff von außen |
| `innodb_buffer_pool_size` | 50–70% des freien RAMs | Hält Tabellendaten und Indizes im schnellen Arbeitsspeicher |
| `innodb_flush_log_at_trx_commit` | `2` | Schreibt Logs im Sekundentakt auf Platte; drastischer I/O-Gewinn |
| `max_connections` | `50–150` | Begrenzt gleichzeitige Verbindungen und verhindert OOM-Abstürze |

Nach der Anpassung starten wir den Dienst neu:

```bash
# MariaDB-Konfiguration neu einlesen
sudo systemctl restart mariadb
```

## 4. PHP 8.3-FPM installieren und abstimmen

PHP wird unter Ubuntu 24.04 in der Version 8.3 bereitgestellt. Der FastCGI Process Manager (`php-fpm`) läuft als eigenständiger Systemdienst und verwaltet Worker-Prozesse, die PHP-Dateien kompilieren und ausführen.

### PHP-FPM und Kernmodule installieren

Für moderne Webapplikationen (wie WordPress, Nextcloud oder Laravel) wird neben dem FPM-Dienst eine Reihe spezialisierter PHP-Erweiterungen benötigt:

```bash
# PHP 8.3-FPM sowie Datenbank-, Bild- und Optimierungsmodule installieren
sudo apt install -y php8.3-fpm php8.3-common php8.3-mysql php8.3-xml                     php8.3-curl php8.3-gd php8.3-mbstring php8.3-zip                     php8.3-opcache php8.3-intl
```

**Dienststatus und Module prüfen:**

```bash
# Status von PHP-FPM kontrollieren
sudo systemctl status php8.3-fpm --no-pager

# Installierte PHP-Version und aktive Erweiterungen listen
php -v
php -m | grep -E '(mysqli|pdo_mysql|opcache|curl)'
```

<blockquote class="infobox infobox--warn">
⚠️ **Komponenten-Abhängigkeit:** Achte zwingend darauf, dass das Paket `php8.3-fpm` installiert ist. Installierst du versehentlich nur das Metapaket `php`, bindet Ubuntu Apache als Abhängigkeit ein und startet einen kollidierenden Webserver.
</blockquote>

### PHP 8.3 Verzeichnisstruktur

```markdown
┌─────────────────────────────────────────────────────────────┐
│                PHP 8.3 Verzeichnisstruktur                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   /etc/php/8.3/                                             │
│   ├── fpm/                                                  │
│   │   ├── php-fpm.conf    Globale Master-Settings           │
│   │   ├── php.ini         FPM-Laufzeitkonfiguration         │
│   │   └── pool.d/         Worker-Pools                      │
│   │       └── www.conf    Standard-Pool (www-data Socket)   │
│   └── cli/                                                  │
│       └── php.ini         CLI-Laufzeitkonfiguration         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Worker-Pool Tuning in `www.conf`

Die Datei `/etc/php/8.3/fpm/pool.d/www.conf` steuert, wie PHP-FPM Prozesse spawnt und Anfragen entgegennimmt. Standardmäßig nutzt Ubuntu den Modus `dynamic`:

```ini
; /etc/php/8.3/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data

; Kommunikation über lokalen Unix-Domain-Socket
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Prozessmanagement
pm = dynamic
pm.max_children = 25
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500
```

**Richtwerte zur Berechnung von `pm.max_children`:**

```bash
# Durchschnittlichen RAM-Bedarf aktiver PHP-FPM-Worker ermitteln (in MB)
ps --no-headers -o rss -C php-fpm8.3 | awk '{total+=$1; count++} END {if (count>0) print int(total/count/1024) " MB"; else print "60 MB (Standard-Richtwert)"}'

# Faustformel zur Berechnung von pm.max_children:
# max_children = (Verfügbarer RAM für PHP in MB) / (Durchschnittlicher RAM pro Worker in MB)

# Rechenbeispiel: 2048 MB zugewiesener RAM bei ca. 60 MB pro Worker
echo $(( 2048 / 60 ))
# Ergebnis: 34
```

| Direktive | Typischer Wert | Funktion |
| --- | --- | --- |
| `pm.max_children` | `20–50` | Maximale Anzahl gleichzeitiger Worker-Prozesse |
| `pm.start_servers` | `4–8` | Anzahl der Worker, die beim Dienststart erzeugt werden |
| `pm.min_spare_servers` | `2–4` | Minimale Reserve an inaktiven Workern bei Leerlauf |
| `pm.max_spare_servers` | `6–12` | Maximale Reserve an inaktiven Workern |
| `pm.max_requests` | `500` | Recycelt Worker nach 500 Requests, um Memory-Leaks zu verhindern |

```bash
# Nach Konfigurationsänderung den FPM-Dienst neu laden
sudo systemctl reload php8.3-fpm
```

## 5. Integration von Nginx und PHP-FPM

Nachdem Nginx und PHP-FPM betriebsbereit sind, konfigurieren wir einen virtuellen Host (Server-Block), der PHP-Anfragen an den Unix-Socket `/run/php/php8.3-fpm.sock` weiterleitet.

```markdown
┌─────────────────────────────────────────────────────────────┐
│                 Nginx & PHP-FPM Request-Flow                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Client-Anfrage (HTTP Port 80 / HTTPS Port 443)            │
│   ▼                                                         │
│   Nginx Webserver                                           │
│   ├── Statische Dateien  ──▶ Direkt ausgeliefert            │
│   └── location ~ \.php$  ──▶ FastCGI-Proxy-Pass             │
│                              │                              │
│                              ▼                              │
│                              Socket: php8.3-fpm.sock        │
│                              │                              │
│                              ▼                              │
│                              PHP-FPM Worker-Prozess         │
│                              │                              │
│                              ▼                              │
│   Client-Antwort         ◀── HTML / JSON Response           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Server-Block anlegen

Wir deaktivieren die Standard-Konfiguration und erstellen einen sauberen Virtual Host unter `/etc/nginx/sites-available/lemp.conf`:

```bash
# Neue Server-Block-Konfiguration anlegen
sudo nano /etc/nginx/sites-available/lemp.conf
```

**Konfigurationsinhalt:**

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name deine-domain.de www.deine-domain.de;

    root /var/www/html;
    index index.php index.html index.htm;

    # Security Header gegen Clickjacking und MIME-Sniffing
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Statische Dateien und Verzeichnisabfragen
    location / {
        try_files $uri $uri/ =404;
    }

    # Dynamische Skripte an den PHP 8.3 FastCGI Unix-Socket leiten
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Buffer-Tuning für dynamische Antworten
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
    }

    # Zugriff auf versteckte Dateien (.git, .env, .htaccess) strikt blockieren
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}
```

### VHost aktivieren und Syntax validieren

```bash
# VHost über Symlink aktivieren
sudo ln -s /etc/nginx/sites-available/lemp.conf /etc/nginx/sites-enabled/

# Veralteten Standard-VHost deaktivieren
sudo rm -f /etc/nginx/sites-enabled/default

# Nginx-Syntax zwingend vor dem Reload prüfen
sudo nginx -t

# Nginx-Konfiguration ohne Downtime neu laden
sudo systemctl reload nginx
```

<blockquote class="infobox infobox--warn">
⚠️ **FastCGI-Stolperfalle:** Erscheint im Browser der Fehler `502 Bad Gateway`, liegt das in 95 % aller Fälle an einem fehlerhaften Socket-Pfad oder Berechtigungsproblemen. Prüfe mit `ls -la /run/php/php8.3-fpm.sock`, ob der Socket existiert und dem Benutzer `www-data:www-data` gehört.
</blockquote>

## 6. End-to-End-Validierung: Datenbank & Webstack

Wir verifizieren das Zusammenspiel aller Komponenten mit einer realen Testdatenbank und einem sicheren PHP-Skript unter Einsatz von Prepared Statements via PDO.

```markdown
┌─────────────────────────────────────────────────────────────┐
│             End-to-End Test: Web, App & Datenbank           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   HTTP GET /db-test.php                                     │
│   ▼                                                         │
│   Nginx Server-Block                                        │
│   ▼                                                         │
│   PHP 8.3-FPM (Unix Domain Socket)                          │
│   ▼                                                         │
│   PDO MySQL Verbindung ────▶ MariaDB (127.0.0.1:3306)       │
│                              ├── UTF8mb4 Verbindung         │
│                              ├── Prepared Statement         │
│                              └── Testdaten verarbeiten      │
│   ▼                                                         │
│   Status-Rückmeldung ──────▶ 200 OK (Text/HTML)             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Dedizierten Testbenutzer in MariaDB anlegen

```bash
# In die MariaDB-Konsole einloggen
sudo mariadb -u root
```

**SQL-Befehle ausführen:**

```sql
CREATE DATABASE lemp_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'lemp_user'@'localhost' IDENTIFIED BY 'EinStrengGeheimesPasswort123!';
GRANT ALL PRIVILEGES ON lemp_test.* TO 'lemp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```

### Sicheres PDO-Testskript erstellen

Wir legen das Testskript unter `/var/www/html/db-test.php` an:

```bash
sudo nano /var/www/html/db-test.php
```

```php
<?php
// /var/www/html/db-test.php - Nur für Verifikationszwecke!
declare(strict_types=1);

$dbConfig = [
    'host'    => '127.0.0.1',
    'dbname'  => 'lemp_test',
    'user'    => 'lemp_user',
    'pass'    => 'EinStrengGeheimesPasswort123!',
    'charset' => 'utf8mb4'
];

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'
];

try {
    $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset={$dbConfig['charset']}";
    $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['pass'], $options);

    // Testtabelle erzeugen
    $pdo->exec("CREATE TABLE IF NOT EXISTS health_check (
        id INT AUTO_INCREMENT PRIMARY KEY,
        checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )");

    // Testdatensatz per Prepared Statement einfügen
    $stmt = $pdo->prepare("INSERT INTO health_check (checked_at) VALUES (NOW())");
    $stmt->execute();

    // Anzahl der Datensätze abrufen
    $count = (int) $pdo->query("SELECT COUNT(*) FROM health_check")->fetchColumn();

    header('Content-Type: text/plain; charset=utf-8');
    echo "LEMP-Stack Health Check: OK
";
    echo "PHP Version: " . PHP_VERSION . "
";
    echo "MariaDB Verbindung: Erfolgreich
";
    echo "Einträge in health_check: {$count}
";

} catch (PDOException $e) {
    http_response_code(500);
    header('Content-Type: text/plain; charset=utf-8');
    echo "LEMP-Stack Health Check: FEHLER
";
    echo "Grund: " . $e->getMessage() . "
";
}
```

```bash
# Dateirechte für den Webserver-Benutzer setzen
sudo chown www-data:www-data /var/www/html/db-test.php
sudo chmod 640 /var/www/html/db-test.php

# Test über die lokale CLI ausführen
curl -s http://localhost/db-test.php
```

Die Ausgabe bestätigt die korrekte Ausführung:
```text
LEMP-Stack Health Check: OK
PHP Version: 8.3.x
MariaDB Verbindung: Erfolgreich
Einträge in health_check: 1
```

<blockquote class="infobox infobox--warn">
⚠️ **Sicherheitsrisiko Testskripte:** Lösche die Datei `db-test.php` unmittelbar nach dem Funktionstest wieder (`sudo rm -f /var/www/html/db-test.php`). Öffentliche Skripte, die Datenbankstruktur oder Verbindungsdetails preisgeben, stellen ein vermeidbares Einfallstor für Angreifer dar.
</blockquote>

## 7. TLS-Verschlüsselung mit Let’s Encrypt & Nginx-Härtung

Ein produktiver Webserver darf heutzutage ausschließlich über verschlüsselte HTTPS-Verbindungen betrieben werden. Wir automatisieren den Bezug und die Verlängerung von TLS-Zertifikaten mit dem EFF-Tool **Certbot**.

```markdown
┌─────────────────────────────────────────────────────────────┐
│            Produktionsreifer HTTPS-Request-Flow             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Client-Browser (HTTPS Port 443)                           │
│   ▼                                                         │
│   TLS 1.3 Handshake & Let’s Encrypt Zertifikatsprüfung      │
│   ▼                                                         │
│   Nginx Webserver mit HSTS & Security-Headern               │
│   ▼                                                         │
│   FastCGI Unix Socket (/run/php/php8.3-fpm.sock)            │
│   ▼                                                         │
│   PHP-FPM Worker-Prozess ──▶ MariaDB (127.0.0.1:3306)       │
│   ▼                                                         │
│   Gzip Komprimierung ──────▶ Antwort an Client-Browser      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Certbot installieren und Zertifikat ausstellen

Die offizielle und am besten gewartete Version von Certbot wird über das Snap-Paketverwaltungssystem bezogen:

```bash
# Snapd sicherstellen und Certbot klassisch installieren
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot

# Symlink im Standard-Pfad erzeugen
sudo ln -sf /snap/bin/certbot /usr/bin/certbot

# Zertifikat anfordern und VHost automatisch für HTTPS konfigurieren
sudo certbot --nginx -d deine-domain.de -d www.deine-domain.de
```

Certbot fragt eine E-Mail-Adresse für Notfall-Benachrichtigungen ab, verifiziert den DNS-Eintrag über eine temporäre HTTP-01-Challenge, passt den Server-Block `/etc/nginx/sites-available/lemp.conf` automatisch auf Port 443 (SSL) an und richtet einen 301-Redirect von HTTP auf HTTPS ein.

### Erweiterte TLS-Härtung und HSTS

Für ein A+-Rating bei SSL Labs ergänzen wir moderne TLS-Protokolle und HTTP Strict Transport Security (HSTS) im SSL-Serverblock:

```nginx
# Ergänzung im Server-Block (Port 443)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

# HSTS (erzwingt 2 Jahre HTTPS im Browser inklusive Subdomains)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
```

```bash
# Konfiguration validieren und Nginx neu laden
sudo nginx -t && sudo systemctl reload nginx

# Automatische Zertifikatserneuerung im Trockenlauf testen
sudo certbot renew --dry-run
```

<blockquote class="infobox infobox--info">
💡 **Automatischer Renewal-Timer:** Certbot richtet bei der Snap-Installation automatisch einen systemd-Timer (`snap.certbot.renew.timer`) ein. Dieser prüft zweimal täglich, ob Zertifikate innerhalb der nächsten 30 Tage ablaufen, und verlängert sie vollautomatisch.
</blockquote>

## Befehlsreferenz (Cheatsheet)

Die wichtigsten operativen Befehle für Verwaltung, Überwachung und Fehlerdiagnose des LEMP-Stacks auf einen Blick:

| Dienst / Aufgabe | Befehl | Erläuterung |
| --- | --- | --- |
| **Pakete & System** | `sudo apt update && sudo apt upgrade -y` | Aktualisiert Paketquellen und installierte Pakete |
| **Firewall (UFW)** | `sudo ufw status verbose` | Zeigt aktive Regeln, Standardrichtlinien und Ports an |
| **Firewall (UFW)** | `sudo ufw allow 'Nginx Full'` | Öffnet HTTP (Port 80) und HTTPS (Port 443) |
| **Nginx Syntax** | `sudo nginx -t` | Prüft Konfigurationsdateien auf Syntax- und Pfadfehler |
| **Nginx Reload** | `sudo systemctl reload nginx` | Übernimmt Konfigurationsänderungen unterbrechungsfrei |
| **MariaDB Shell** | `sudo mariadb -u root -p` | Öffnet die administrative Datenbank-Konsole |
| **MariaDB Status** | `sudo systemctl status mariadb` | Überprüft Prozessstatus und Uptime des Datenbankservers |
| **PHP-FPM Status** | `sudo systemctl status php8.3-fpm` | Kontrolliert den Status des PHP-Prozessmanagers |
| **PHP-FPM Socket** | `ls -la /run/php/php8.3-fpm.sock` | Verifiziert Existenz und Berechtigung (`www-data`) des Sockets |
| **TLS-Zertifikat** | `sudo certbot --nginx -d deine-domain.de` | Bezieht und installiert Let's Encrypt TLS-Zertifikate |
| **TLS-Erneuerung** | `sudo certbot renew --dry-run` | Simuliert die automatische Verlängerung aller Zertifikate |
| **Netzwerk-Ports** | `sudo ss -tuln \| grep -E ':(80\|443\|3306)'` | Kontrolliert gebundene Ports von Nginx und MariaDB |

## Weiterführende Ressourcen

Die folgenden internen Dokumentationen und Referenzen vertiefen den sicheren Betrieb, die Härtung und das Systemmanagement:

| Ressource / Dokumentation | Beschreibung |
| --- | --- |
| [Ubuntu-Upgrade: 22.04 auf 24.04 LTS](/de/serverumgebungen/ubuntu-upgrade-von-version-22-04-lts-auf-24-04-lts){.badge-link-text} | Grundlagen zum LTS-Releasezyklus, In-Place-Upgrades und Kernel-Features |
| [LEMP-Stack auf Ubuntu 26.04 LTS](/de/serverumgebungen/ubuntu-26-04-lemp-stack-installieren){.badge-link-text} | Nachfolge-Leitfaden für Ubuntu 26.04 mit Nginx HTTP/3, MariaDB 11.4 und PHP 8.5 |
| [Linux Server Härtung: SSH & Firewall](/de/serverumgebungen/linux-server-haertung-fido2-crowdsec){.badge-link-text} | Erweiterte Absicherung des Servers mit SSH-Key-Zwang, CrowdSec und Fail2ban |
| [Virtuelle Maschinen für Tests](/de/linux-administration/linux-administration-virtualisierung-und-vm-management){.badge-link-text} | Isolierte Testumgebungen für Staging-Webserver unter KVM und Proxmox VE |
| [Docker & Container-Workloads auf Linux](/de/serverumgebungen/alma-linux-wie-man-docker-und-docker-compose-installiert){.badge-link-text} | Container-basierte Bereitstellung von Webanwendungen und Microservices |
| [Ubuntu Serverumgebungen](/de/category/serverumgebungen){.badge-link-text} | Übersicht aller Leitfäden für Administration, Wartung und Betrieb von Linux-Servern |
| [Offizielle Nginx-Dokumentation](https://nginx.org/en/docs/){.badge-link-text} | Referenzhandbuch zu allen Modulen, Upstream-Direktiven und Tuning-Parametern |
| [Offizielle MariaDB-Dokumentation](https://mariadb.com/docs/){.badge-link-text} | Handbuch zu InnoDB-Engine, Replikation und Benutzer-Privilegien |
| [Offizielle PHP-FPM-Dokumentation](https://www.php.net/docs.php){.badge-link-text} | Leitfaden zu Prozess-Managern, Pool-Direktiven und OPcache-Konfiguration |

## Fazit

Mit dem hier aufgebauten LEMP-Stack steht auf Ubuntu 24.04 LTS eine performante, ressourcenschonende und gehärtete Plattform für moderne Webanwendungen bereit. Durch das Zusammenspiel von Nginx als asynchronem Webserver, PHP 8.3-FPM über lokale Unix-Domain-Sockets und einem abgesicherten MariaDB 10.11 LTS-Server sind alle Komponenten sauber voneinander entkoppelt. Das Zusammenspiel aus strikten UFW-Firewallregeln, Fail2ban und automatisierter Let’s Encrypt-Verschlüsselung sorgt für ein stabiles Sicherheitsfundament im Produktivbetrieb.

<blockquote class="infobox infobox--info">
💡 **Praxistipp für den Produktivbetrieb:** Überwache die Auslastung deiner PHP-FPM-Worker in `/etc/php/8.3/fpm/pool.d/www.conf`. Bei steigenden Besucherzahlen sollten `pm.max_children`, `pm.start_servers` und `pm.max_spare_servers` an den verfügbaren Arbeitsspeicher angepasst werden, um Worker-Engpässe (`server reached max_children setting`) zu verhindern. Richte zudem in `php.ini` den OPcache mit mindestens 128 MB Speicher ein, um den Bytecode kompilierter PHP-Skripte im RAM zu halten und die Latenz drastisch zu senken.
</blockquote>

Für den langfristigen Betrieb empfiehlt es sich, die Plattform schrittweise um ein zentrales Monitoring und automatisierte Log-Rotation zu ergänzen. Im nächsten Schritt kannst du deine Webserver-Infrastruktur mit fortgeschrittenen Sicherheitsmaßnahmen wie CrowdSec oder einer Multi-VHost-Architektur weiter ausbauen.
