Webserver-Tuning ist entscheidend für die Performance bei hohen Besucherzahlen. Die richtige Konfiguration von Worker-Prozessen, Connections und Caching macht den Unterschied.

Grundlagen

Performance-Faktoren

| Faktor | Einfluss | |--------|----------| | Worker/Connections | Gleichzeitige Anfragen | | Keep-Alive | Verbindungswiederverwendung | | Caching | Reduktion von Berechnungen | | Kompression | Bandbreitenersparnis | | Timeouts | Ressourcenfreigabe |

System-Limits prüfen

# Offene Dateien
ulimit -n

# Max User Processes
ulimit -u

# Kernel-Limits
cat /proc/sys/fs/file-max
cat /proc/sys/net/core/somaxconn

Nginx-Tuning

Worker-Konfiguration

# /etc/nginx/nginx.conf

# Worker-Prozesse (= CPU-Kerne)
worker_processes auto;

# Worker-Connections
events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

Berechnung

Max Clients = worker_processes × worker_connections
Beispiel: 4 × 4096 = 16.384 gleichzeitige Verbindungen

Allgemeine Optimierungen

# /etc/nginx/nginx.conf

worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # Basics
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Timeouts
    keepalive_timeout 30;
    keepalive_requests 1000;
    send_timeout 30;
    client_body_timeout 30;
    client_header_timeout 30;

    # Buffer
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    client_max_body_size 16m;
    large_client_header_buffers 4 16k;

    # Hash Tables
    types_hash_max_size 2048;
    server_names_hash_bucket_size 64;

    # Logging
    access_log off;
    # Oder: nur Fehler loggen
    # access_log /var/log/nginx/access.log combined buffer=512k flush=1m;
    error_log /var/log/nginx/error.log warn;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_comp_level 5;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Statische Dateien optimieren

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # Statische Dateien cachen
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # Open File Cache
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

Proxy-Optimierung

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend;

        # Connection Reuse
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 32k;
        proxy_busy_buffers_size 64k;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Cache
        proxy_cache_valid 200 10m;
    }
}

FastCGI-Optimierung (PHP)

upstream php-fpm {
    server unix:/run/php/php8.2-fpm.sock;
}

server {
    location ~ \.php$ {
        fastcgi_pass php-fpm;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Buffering
        fastcgi_buffering on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 16 16k;

        # Timeouts
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 180s;
        fastcgi_read_timeout 180s;

        # Cache
        fastcgi_cache_valid 200 60m;
    }
}

Apache-Tuning

MPM-Auswahl

# Aktives MPM prüfen
apachectl -V | grep MPM

# MPM wechseln
a2dismod mpm_prefork
a2enmod mpm_event
systemctl restart apache2

MPM Event (empfohlen)

# /etc/apache2/mods-available/mpm_event.conf

<IfModule mpm_event_module>
    StartServers             4
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadsPerChild         64
    MaxRequestWorkers      400
    MaxConnectionsPerChild  10000
</IfModule>

MPM Worker

# /etc/apache2/mods-available/mpm_worker.conf

<IfModule mpm_worker_module>
    StartServers             4
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadsPerChild         25
    MaxRequestWorkers      400
    MaxConnectionsPerChild  10000
</IfModule>

MPM Prefork (für mod_php)

# /etc/apache2/mods-available/mpm_prefork.conf

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers          5
    MaxSpareServers         10
    MaxRequestWorkers      150
    MaxConnectionsPerChild  3000
</IfModule>

Berechnung MaxRequestWorkers

MaxRequestWorkers = (Verfügbarer RAM - System-RAM) / RAM pro Worker

Beispiel:
- 8 GB RAM gesamt
- 2 GB für System
- ~50 MB pro Apache-Worker
MaxRequestWorkers = (8000 - 2000) / 50 = 120

Keep-Alive-Konfiguration

# /etc/apache2/apache2.conf

KeepAlive On
MaxKeepAliveRequests 500
KeepAliveTimeout 5

Allgemeine Optimierungen

# /etc/apache2/conf-available/performance.conf

# Hostname Lookups deaktivieren
HostnameLookups Off

# Timeout reduzieren
Timeout 60

# Keine Signaturen
ServerSignature Off
ServerTokens Prod

# ETags
FileETag None

# Symlinks
Options +FollowSymLinks -SymLinksIfOwnerMatch

mod_deflate (Kompression)

# /etc/apache2/mods-available/deflate.conf

<IfModule mod_deflate.c>
    SetOutputFilter DEFLATE

    # Komprimierbare Typen
    AddOutputFilterByType DEFLATE text/html text/plain text/xml
    AddOutputFilterByType DEFLATE text/css text/javascript
    AddOutputFilterByType DEFLATE application/javascript application/json
    AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
    AddOutputFilterByType DEFLATE font/opentype font/otf font/ttf

    # Bereits komprimierte Dateien ausschließen
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|zip|gz|bz2|rar)$ no-gzip

    # Problematische Browser
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
</IfModule>

mod_expires (Caching)

# /etc/apache2/mods-available/expires.conf

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 1 month"

    # HTML
    ExpiresByType text/html "access plus 1 hour"

    # CSS/JavaScript
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"

    # Bilder
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"

    # Fonts
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
</IfModule>

PHP-FPM-Tuning

Pool-Konfiguration

; /etc/php/8.2/fpm/pool.d/www.conf

[www]
user = www-data
group = www-data

; Socket-Verbindung (schneller)
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process Manager
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000

; Timeouts
request_terminate_timeout = 180

Process Manager Modi

| Modus | Beschreibung | Verwendung | |-------|--------------|------------| | static | Feste Anzahl Worker | Konstante Last | | dynamic | Skaliert nach Bedarf | Standard | | ondemand | Nur bei Bedarf | Wenig Traffic |

Berechnung pm.max_children

# Durchschnittlichen RAM pro PHP-Prozess ermitteln
ps -ylC php-fpm8.2 --sort:rss | awk '{sum+=$8; count++} END {print sum/count/1024 " MB"}'

# max_children = (Verfügbarer RAM - System) / RAM pro Prozess
# Beispiel: (4000 MB - 1000 MB) / 50 MB = 60

OPcache konfigurieren

; /etc/php/8.2/fpm/conf.d/10-opcache.ini

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.validate_timestamps=1
opcache.save_comments=1
opcache.fast_shutdown=1

; Preloading (PHP 7.4+)
; opcache.preload=/var/www/html/preload.php
; opcache.preload_user=www-data

System-Tuning

Kernel-Parameter

# /etc/sysctl.d/99-webserver.conf

# Netzwerk-Buffer
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535

# TCP-Optimierungen
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Lokale Ports
net.ipv4.ip_local_port_range = 1024 65535

# Memory
vm.swappiness = 10

# File Descriptors
fs.file-max = 2097152
sysctl -p /etc/sysctl.d/99-webserver.conf

Limits für Webserver

# /etc/security/limits.d/webserver.conf

www-data soft nofile 65535
www-data hard nofile 65535
www-data soft nproc 65535
www-data hard nproc 65535

Systemd Service-Limits

# /etc/systemd/system/nginx.service.d/limits.conf
# oder /etc/systemd/system/apache2.service.d/limits.conf

[Service]
LimitNOFILE=65535
LimitNPROC=65535
systemctl daemon-reload
systemctl restart nginx

Benchmarking

Apache Bench (ab)

# 1000 Requests, 100 gleichzeitig
ab -n 1000 -c 100 http://localhost/

# Mit Keep-Alive
ab -n 1000 -c 100 -k http://localhost/

# POST-Request
ab -n 1000 -c 50 -p data.json -T application/json http://localhost/api

wrk

# Installation
apt install wrk

# Benchmark
wrk -t4 -c100 -d30s http://localhost/

# Mit Skript
wrk -t4 -c100 -d30s -s post.lua http://localhost/api

siege

# Installation
apt install siege

# Benchmark
siege -c 100 -t 30S http://localhost/

# URLs aus Datei
siege -c 100 -t 30S -f urls.txt

Monitoring

Nginx Status

# /etc/nginx/conf.d/status.conf

server {
    listen 127.0.0.1:8080;

    location /nginx_status {
        stub_status on;
        access_log off;
    }
}
curl http://127.0.0.1:8080/nginx_status

Apache Status

# /etc/apache2/mods-available/status.conf

<IfModule mod_status.c>
    <Location /server-status>
        SetHandler server-status
        Require ip 127.0.0.1
    </Location>
    ExtendedStatus On
</IfModule>
a2enmod status
systemctl restart apache2
curl http://127.0.0.1/server-status?auto

PHP-FPM Status

; /etc/php/8.2/fpm/pool.d/www.conf

pm.status_path = /fpm-status
ping.path = /fpm-ping
location ~ ^/(fpm-status|fpm-ping)$ {
    access_log off;
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include fastcgi_params;
}

Zusammenfassung

| Nginx | Empfehlung | |-------|------------| | worker_processes | auto (= CPU-Kerne) | | worker_connections | 2048-4096 | | keepalive_timeout | 30s | | gzip | on |

| Apache MPM | Empfehlung | |------------|------------| | MPM Event | Bevorzugt | | MaxRequestWorkers | RAM-basiert berechnen | | KeepAliveTimeout | 5s | | MaxKeepAliveRequests | 500 |

| PHP-FPM | Empfehlung | |---------|------------| | pm | dynamic | | pm.max_children | RAM-basiert | | OPcache | Aktiviert |

| System | Einstellung | |--------|-------------| | somaxconn | 65535 | | file-max | 2097152 | | swappiness | 10 |

Fazit

Webserver-Tuning erfordert eine ganzheitliche Betrachtung von Webserver, PHP und Betriebssystem. Die wichtigsten Stellschrauben sind Worker-Konfiguration, Keep-Alive-Einstellungen und Caching. Benchmarks helfen, Optimierungen zu validieren. Die Konfiguration muss an die verfügbare Hardware und das Traffic-Muster angepasst werden. Regelmäßiges Monitoring deckt Engpässe frühzeitig auf.