Webserver Nginx

22. Feber 2026
//

1. Überblick – WordPress‑Stack imatrix.at

Komponenten:

  • Nginx 1.22.1 (OpenSSL 3.0.x, HTTP/2, hardened Build)
  • PHP‑FPM 8.2 (Unix‑Socket)
  • Redis (lokal, protected‑mode)
  • FastCGI‑Cache (Nginx)
  • Hardening‑Layer (Nginx‑Snippets)
  • Security‑Header & Rate‑Limits
  • Certbot‑TLS

Architektur:

Client → Nginx (TLS, Hardening, FastCGI‑Cache) → PHP‑FPM → WordPress → Redis (Object Cache) → zurück über Nginx zum Client.


2. Nginx Hauptkonfiguration (`/etc/nginx/nginx.conf`)

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 64M;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    server_tokens off;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" rt=$request_time '
                    'cache=$upstream_cache_status';

    access_log /var/log/nginx/access.log main;
    error_log  /var/log/nginx/error.log warn;

    include /etc/nginx/conf.d/security.conf;
    include /etc/nginx/conf.d/cache.conf;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml \
               application/xml application/xml+rss text/javascript image/svg+xml;
    gzip_min_length 1024;

    include /etc/nginx/sites-enabled/*;

    server {
        location ~* \.(?:sql|log|sh|bak|conf)$ { deny all; }
        location ~* /(?:\.git|\.env|composer\.json|composer\.lock) { deny all; }
    }
}

3. Sites Enabled / Available

3.1 Default‑Deny (`/etc/nginx/sites-available/00-default-deny.conf`)

server {
    listen 80 default_server;
    server_name _;
    access_log /var/log/nginx/scanners.log;
    return 444;
}

server {
    listen 443 ssl default_server;
    server_name _;

    ssl_certificate     /etc/letsencrypt/live/imatrix.at/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/imatrix.at/privkey.pem;

    access_log /var/log/nginx/scanners.log;
    return 444;
}

3.2 imatrix.at (`/etc/nginx/sites-available/imatrix.at`)

map $http_pragma $nocache { default 0; "~*no-cache" 1; }
map $http_cache_control $nocache2 { default 0; "~*no-cache" 1; }

server {
    listen 443 ssl http2;
    server_name imatrix.at www.imatrix.at i-matrix.at www.i-matrix.at;

    root /var/www/imatrix.at/public;
    index index.php;

    access_log /var/log/nginx/imatrix.access.log main;
    error_log  /var/log/nginx/imatrix.error.log warn;

    include snippets/hardening.conf;

    location ~* \.(css|js|jpg|jpeg|gif|png|svg|webp|ico|ttf|otf|woff|woff2)$ {
        expires 30d;
        access_log off;
    }

    location ~* /wp-content/uploads/.*\.php$ {
        deny all;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~* wp-config.php { deny all; }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm-imatrix.sock;

        set $skip_cache 0;
        if ($request_method = POST) { set $skip_cache 1; }
        if ($query_string != "") { set $skip_cache 1; }
        if ($http_cookie ~* "wordpress_logged_in_|comment_author|woocommerce_items_in_cart") { set $skip_cache 1; }
        if ($nocache = 1) { set $skip_cache 1; }
        if ($nocache2 = 1) { set $skip_cache 1; }

        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache     $skip_cache;
        fastcgi_cache WORDPRESS;
        fastcgi_cache_valid 200 301 302 10m;

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT  $realpath_root;
    }

    location = /wp-login.php {
        limit_req zone=wpadmin burst=20 nodelay;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm-imatrix.sock;
    }

    location ^~ /.well-known/acme-challenge/ {
        allow all;
    }

    ssl_certificate     /etc/letsencrypt/live/imatrix.at/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/imatrix.at/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;

    add_header Strict-Transport-Security "max-age=31536000" always;
}

server {
    listen 80;
    server_name imatrix.at www.imatrix.at i-matrix.at www.i-matrix.at;
    return 301 https://$host$request_uri;
}

4. conf.d Layer

4.1 Cache (`/etc/nginx/conf.d/cache.conf`)

fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2
    keys_zone=WORDPRESS:100m inactive=60m use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

map $upstream_cache_status $cache_status {
    default "";
    HIT     "HIT";
    MISS    "MISS";
    BYPASS  "BYPASS";
    EXPIRED "EXPIRED";
}

add_header X-Cache $cache_status;

4.2 Security (`/etc/nginx/conf.d/security.conf`)

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

limit_req_zone $binary_remote_addr zone=wpadmin:10m rate=10r/m;

5. Hardening Snippet (`/etc/nginx/snippets/hardening.conf`)

location ~* \.env(\.|$) {
    access_log /var/log/nginx/scanners.log;
    log_not_found off;
    return 444;
}

location ~* ^/(phpinfo(\.php)?|app_dev\.php|_profiler)(/|$) {
    access_log /var/log/nginx/scanners.log;
    log_not_found off;
    return 444;
}

location ~ /\.(?!well-known).* {
    access_log /var/log/nginx/scanners.log;
    log_not_found off;
    return 444;
}

location = /.git {
    access_log /var/log/nginx/scanners.log;
    return 444;
}
location = /.svn {
    access_log /var/log/nginx/scanners.log;
    return 444;
}
location = /.hg {
    access_log /var/log/nginx/scanners.log;
    return 444;
}

location ~* \.(bak|old|orig|save|swp|swo|tmp)$ {
    access_log /var/log/nginx/scanners.log;
    log_not_found off;
    return 444;
}

6. PHP‑FPM Pool (`/etc/php/8.2/fpm/pool.d/www.conf` – exemplarisch)

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

listen = /run/php/php8.2-fpm-imatrix.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

pm.max_requests = 500

process.priority = -19
process.dumpable = yes

php_admin_value[error_log] = /var/log/php8.2-fpm.log
php_admin_flag[log_errors] = on

7. Redis Konfiguration (`/etc/redis/redis.conf` – relevante Auszüge)

bind 127.0.0.1 -::1
protected-mode yes
port 6379

daemonize yes
pidfile /run/redis/redis-server.pid

loglevel notice
logfile /var/log/redis/redis-server.log

databases 16

save 3600 1 300 100 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis

8. Cache‑Flow in Worten (für dein Diagramm)

  • Schritt 1:
    Client sendet HTTP(S)‑Request an Nginx.
  • Schritt 2:
    Nginx prüft: statische Datei? → Wenn ja, direkt ausliefern.
  • Schritt 3:
    Wenn PHP‑Request: FastCGI‑Cache prüft Cache‑Key.
  • Schritt 4:
    Wenn Cache‑HIT → Antwort direkt aus Cache an Client.
  • Schritt 5:
    Wenn Cache‑MISS → Request an PHP‑FPM → WordPress.
  • Schritt 6:
    WordPress nutzt Redis als Object‑Cache für DB‑Queries.
  • Schritt 7:
    Antwort von WordPress zurück zu Nginx.
  • Schritt 8:
    Nginx speichert Antwort (wenn nicht skip_cache) im FastCGI‑Cache.
  • Schritt 9:
    Antwort geht an Client, inkl. X-Cache‑Header (HIT/MISS/BYPASS).

Leave a Comment