Przejdź do treści
DevOps

Nginx vs Apache - Konfiguracja produkcyjna serwerów WWW

Opublikowano:
·4 min czytania·Autor: MDS Software Solutions Group

Nginx Apache Konfiguracja

devops

Nginx vs Apache - Konfiguracja produkcyjna serwerów WWW

Wybór serwera WWW to jedna z najważniejszych decyzji infrastrukturalnych w każdym projekcie webowym. Nginx i Apache dominują rynek od lat, ale fundamentalnie różnią się architekturą, sposobem obsługi żądań i scenariuszami, w których sprawdzają się najlepiej. W tym artykule przeprowadzimy kompleksowe porównanie obu serwerów, prezentując gotowe konfiguracje produkcyjne, optymalizacje wydajności oraz rekomendacje dotyczące tego, kiedy wybrać który serwer.

Architektura - event-driven vs process-based#

Fundamentalna różnica między Nginx i Apache leży w sposobie obsługi połączeń przychodzących. Zrozumienie tej różnicy jest kluczowe dla prawidłowej konfiguracji produkcyjnej.

Nginx - architektura event-driven#

Nginx wykorzystuje asynchroniczny, sterowany zdarzeniami model obsługi połączeń. Jeden proces worker może obsługiwać tysiące jednoczesnych połączeń dzięki nieblokującemu I/O i pętli zdarzeń (epoll na Linuxie, kqueue na BSD).

# /etc/nginx/nginx.conf - Konfiguracja główna
user www-data;
worker_processes auto;          # Jeden worker na rdzeń CPU
worker_rlimit_nofile 65535;     # Limit otwartych plików
pid /run/nginx.pid;

events {
    worker_connections 4096;    # Połączenia na worker
    multi_accept on;            # Akceptuj wiele połączeń jednocześnie
    use epoll;                  # Mechanizm event na Linux
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;          # Ukryj wersję serwera

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

    # Logowanie
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

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

W tym modelu pojedynczy worker process z 4096 połączeniami i przy 4 rdzeniach CPU może teoretycznie obsłużyć do 16 384 jednoczesnych połączeń - bez tworzenia nowego procesu czy wątku dla każdego żądania.

Apache - architektura process/thread-based#

Apache tradycyjnie wykorzystuje model prefork (osobny proces na każde żądanie) lub worker/event MPM (połączenie procesów i wątków). W nowoczesnych instalacjach zalecany jest event MPM:

# /etc/apache2/mods-available/mpm_event.conf
<IfModule mpm_event_module>
    StartServers             4
    MinSpareThreads          25
    MaxSpareThreads          75
    ThreadLimit              64
    ThreadsPerChild          25
    MaxRequestWorkers        400
    MaxConnectionsPerChild   10000
    ServerLimit              16
</IfModule>

Apache z event MPM jest znacznie wydajniejszy niż prefork, ale nadal wymaga więcej zasobów na każde połączenie niż Nginx. Każdy MaxRequestWorkers odpowiada jednemu jednocześnie obsługiwanemu żądaniu.

Nginx jako reverse proxy#

Nginx najczęściej wykorzystywany jest jako reverse proxy przed serwerami aplikacyjnymi. Oto kompletna konfiguracja produkcyjna:

# /etc/nginx/sites-available/app.conf
upstream backend_app {
    least_conn;                              # Load balancing - najmniej połączeń
    server 127.0.0.1:3000 weight=3;          # Główna instancja
    server 127.0.0.1:3001 weight=2;          # Dodatkowa instancja
    server 127.0.0.1:3002 backup;            # Backup - tylko gdy główne padną

    keepalive 32;                            # Pula połączeń keep-alive
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL - konfiguracja poniżej
    include /etc/nginx/snippets/ssl-params.conf;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Rozmiar ciała żądania
    client_max_body_size 64M;
    client_body_buffer_size 128k;

    # Proxy do backendu
    location / {
        proxy_pass http://backend_app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouty proxy
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Buforowanie
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }

    # Pliki statyczne bezpośrednio z Nginx
    location /static/ {
        alias /var/www/app/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location /_next/static/ {
        alias /var/www/app/.next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

Apache - Virtual Hosts i .htaccess#

Apache oferuje potężny system konfiguracji oparty na Virtual Hosts i plikach .htaccess:

# /etc/apache2/sites-available/app.conf
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/app/public

    # SSL
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf

    # Logowanie
    ErrorLog ${APACHE_LOG_DIR}/app-error.log
    CustomLog ${APACHE_LOG_DIR}/app-access.log combined

    # Katalog główny
    <Directory /var/www/app/public>
        AllowOverride All
        Require all granted
        Options -Indexes +FollowSymLinks
    </Directory>

    # Proxy do Node.js / Next.js
    ProxyPreserveHost On
    ProxyPass /api http://127.0.0.1:3000/api
    ProxyPassReverse /api http://127.0.0.1:3000/api

    # Pliki statyczne
    <LocationMatch "^/static/">
        ExpiresActive On
        ExpiresDefault "access plus 1 year"
        Header set Cache-Control "public, immutable"
    </LocationMatch>

    # Nagłówki bezpieczeństwa
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</VirtualHost>

Plik .htaccess dla aplikacji PHP/Laravel:

# /var/www/app/public/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Przekierowanie na HTTPS
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # Usunięcie trailing slash
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Front controller - Laravel
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

# Kompresja
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css
    AddOutputFilterByType DEFLATE text/javascript application/javascript
    AddOutputFilterByType DEFLATE application/json application/xml
    AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>

# Cache przeglądarki
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

SSL/TLS z Let's Encrypt#

Konfiguracja SSL jest krytyczna dla bezpieczeństwa i SEO. Oto optymalne ustawienia dla obu serwerów.

Nginx - SSL snippet#

# /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# Sesje SSL
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# HSTS - Strict Transport Security
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Diffie-Hellman
ssl_dhparam /etc/nginx/dhparam.pem;

Generowanie parametrów DH:

openssl dhparam -out /etc/nginx/dhparam.pem 4096

Automatyczne odnawianie certyfikatów#

# Certbot z automatycznym odnowieniem
sudo certbot --nginx -d example.com -d www.example.com

# Crontab - odnowienie co 12h
0 */12 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

PHP-FPM - konfiguracja dla Nginx i Apache#

PHP-FPM (FastCGI Process Manager) to zalecany sposób uruchamiania PHP zarówno z Nginx, jak i Apache.

Konfiguracja puli PHP-FPM#

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

; Socket UNIX - szybszy niż TCP
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data

; Zarządzanie procesami
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000

; Slow log do debugowania
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s

; Limity
request_terminate_timeout = 120s
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M

; OPcache
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_value[opcache.validate_timestamps] = 0

Nginx z PHP-FPM#

server {
    listen 443 ssl http2;
    server_name laravel-app.com;
    root /var/www/laravel/public;
    index index.php;

    include /etc/nginx/snippets/ssl-params.conf;
    ssl_certificate /etc/letsencrypt/live/laravel-app.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/laravel-app.com/privkey.pem;

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

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;

        # Buforowanie FastCGI
        fastcgi_buffering on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 16 16k;

        # Timeouty
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 120;
        fastcgi_read_timeout 120;
    }

    # Blokowanie dostępu do plików ukrytych
    location ~ /\.(?!well-known) {
        deny all;
    }

    # Blokowanie dostępu do wrażliwych plików
    location ~* \.(env|log|git|sql|bak)$ {
        deny all;
        return 404;
    }
}

Apache z PHP-FPM#

<VirtualHost *:443>
    ServerName laravel-app.com
    DocumentRoot /var/www/laravel/public

    # PHP-FPM przez proxy
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>

    <Directory /var/www/laravel/public>
        AllowOverride All
        Require all granted
        Options -Indexes
    </Directory>

    # Blokowanie dostępu do plików ukrytych
    <DirectoryMatch "/\.">
        Require all denied
    </DirectoryMatch>
</VirtualHost>

Load balancing#

Nginx - zaawansowany load balancing#

upstream php_backend {
    # Algorytm - ip_hash zapewnia sticky sessions
    ip_hash;

    server 10.0.0.10:9000 weight=5 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:9000 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:9000 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:9000 backup;

    keepalive 64;
}

upstream node_backend {
    least_conn;

    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;

    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    location /api/ {
        proxy_pass http://php_backend;
        proxy_next_upstream error timeout http_500 http_502 http_503;
        proxy_next_upstream_tries 3;
    }

    location / {
        proxy_pass http://node_backend;
        proxy_next_upstream error timeout http_502;
    }
}

Apache - mod_proxy_balancer#

<Proxy "balancer://app_cluster">
    BalancerMember "http://10.0.0.10:8080" route=node1 loadfactor=5
    BalancerMember "http://10.0.0.11:8080" route=node2 loadfactor=3
    BalancerMember "http://10.0.0.12:8080" route=node3 loadfactor=2 status=+H
    ProxySet lbmethod=byrequests stickysession=JSESSIONID
</Proxy>

<VirtualHost *:443>
    ServerName app.example.com

    ProxyPreserveHost On
    ProxyPass / "balancer://app_cluster/"
    ProxyPassReverse / "balancer://app_cluster/"

    # Panel zarządzania load balancerem
    <Location "/balancer-manager">
        SetHandler balancer-manager
        Require ip 10.0.0.0/8
    </Location>
</VirtualHost>

Konfiguracja cache#

Nginx - FastCGI cache i proxy cache#

# Definicja strefy cache (w bloku http)
fastcgi_cache_path /var/cache/nginx/fastcgi
    levels=1:2
    keys_zone=PHPCACHE:100m
    max_size=2g
    inactive=60m
    use_temp_path=off;

proxy_cache_path /var/cache/nginx/proxy
    levels=1:2
    keys_zone=PROXYCACHE:100m
    max_size=5g
    inactive=24h
    use_temp_path=off;

server {
    # FastCGI cache dla PHP
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;

        fastcgi_cache PHPCACHE;
        fastcgi_cache_valid 200 301 302 60m;
        fastcgi_cache_valid 404 1m;
        fastcgi_cache_key "$scheme$request_method$host$request_uri";
        fastcgi_cache_use_stale error timeout updating http_500 http_503;
        fastcgi_cache_bypass $cookie_nocache $arg_nocache;

        add_header X-Cache-Status $upstream_cache_status;
    }

    # Proxy cache dla API
    location /api/public/ {
        proxy_pass http://backend_app;
        proxy_cache PROXYCACHE;
        proxy_cache_valid 200 10m;
        proxy_cache_key "$request_uri";
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Apache - mod_cache#

# Włączanie modułów cache
# a2enmod cache cache_disk headers

CacheQuickHandler off
CacheLock on
CacheLockPath /tmp/mod_cache-lock
CacheLockMaxAge 5

<VirtualHost *:443>
    CacheEnable disk /
    CacheRoot /var/cache/apache2/mod_cache_disk
    CacheDefaultExpire 3600
    CacheMaxExpire 86400
    CacheIgnoreNoLastMod On

    # Nie cache'uj panelu admina
    CacheDisable /admin
    CacheDisable /api/auth

    # Cache headers
    <LocationMatch "^/static/">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </LocationMatch>
</VirtualHost>

Kompresja gzip i Brotli#

Nginx#

# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
    text/plain
    text/css
    text/javascript
    application/javascript
    application/json
    application/xml
    application/xml+rss
    image/svg+xml
    font/woff2;

# Brotli (wymaga modułu ngx_brotli)
brotli on;
brotli_comp_level 6;
brotli_static on;          # Serwuj pre-kompresowane pliki .br
brotli_types
    text/plain
    text/css
    text/javascript
    application/javascript
    application/json
    application/xml
    image/svg+xml
    font/woff2;

Apache#

# mod_deflate (gzip)
<IfModule mod_deflate.c>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 5

    AddOutputFilterByType DEFLATE text/html text/plain text/css
    AddOutputFilterByType DEFLATE text/javascript application/javascript
    AddOutputFilterByType DEFLATE application/json application/xml
    AddOutputFilterByType DEFLATE image/svg+xml font/woff2

    # Nie kompresuj już skompresowanych plików
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|gz|zip|br)$ no-gzip
</IfModule>

# Brotli (wymaga mod_brotli - Apache 2.4.26+)
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css
    AddOutputFilterByType BROTLI_COMPRESS text/javascript application/javascript
    AddOutputFilterByType BROTLI_COMPRESS application/json application/xml
    BrotliCompressionQuality 5
</IfModule>

Nagłówki bezpieczeństwa#

Nginx - kompletna konfiguracja security headers#

# /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://api.example.com;" always;

# Usunięcie nagłówków ujawniających technologię
proxy_hide_header X-Powered-By;
fastcgi_hide_header X-Powered-By;

Rate limiting#

Nginx#

# Definicja stref rate limitingu (w bloku http)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    # Ogólny rate limit dla API
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://backend_app;
    }

    # Ścisły rate limit dla logowania
    location /api/auth/login {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;
        proxy_pass http://backend_app;
    }

    # Limit jednoczesnych połączeń
    location /downloads/ {
        limit_conn addr 5;
        limit_rate 1m;          # 1MB/s na połączenie
        alias /var/www/downloads/;
    }
}

Apache - mod_ratelimit i mod_evasive#

# mod_ratelimit - limit przepustowości
<Location "/downloads">
    SetOutputFilter RATE_LIMIT
    SetEnv rate-limit 1024       # 1024 KB/s
</Location>

# mod_evasive - ochrona przed DDoS
<IfModule mod_evasive20.c>
    DOSHashTableSize 3097
    DOSPageCount 5               # Max 5 żądań na stronę/s
    DOSSiteCount 50              # Max 50 żądań na stronę/s
    DOSPageInterval 1
    DOSSiteInterval 1
    DOSBlockingPeriod 60         # Blokada na 60s
    DOSEmailNotify admin@example.com
</IfModule>

WebSocket proxying#

Nginx#

# Mapa do obsługi WebSocket upgrade
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # Timeout dla WebSocket (dłuższy niż standardowy)
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Apache#

# Wymagane moduły: mod_proxy_wstunnel
# a2enmod proxy_wstunnel

<VirtualHost *:443>
    # WebSocket proxy
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /ws/(.*) ws://127.0.0.1:3000/ws/$1 [P,L]

    ProxyPass /ws/ ws://127.0.0.1:3000/ws/
    ProxyPassReverse /ws/ ws://127.0.0.1:3000/ws/

    # Timeout dla WebSocket
    ProxyTimeout 3600
</VirtualHost>

Porównanie wydajności#

Poniżej prezentujemy typowe wyniki benchmarków dla obu serwerów w różnych scenariuszach:

| Metryka | Nginx | Apache (event MPM) | |---------|-------|---------------------| | Pliki statyczne (req/s) | ~25 000 | ~10 000 | | Reverse proxy (req/s) | ~18 000 | ~8 000 | | PHP-FPM (req/s) | ~3 500 | ~3 200 | | Zużycie RAM (1000 conn.) | ~50 MB | ~200 MB | | Zużycie RAM (10000 conn.) | ~80 MB | ~1.5 GB | | Czas odpowiedzi p99 | ~2 ms | ~8 ms | | Max. jednoczesnych połączeń | ~100 000+ | ~10 000 |

Nginx dominuje w scenariuszach z wieloma jednocznymi połączeniami i serwowaniem plików statycznych. Przy dynamicznych żądaniach PHP-FPM różnica jest mniejsza, ponieważ wąskim gardłem staje się sam PHP.

Nginx dla Next.js / Node.js#

# Produkcyjna konfiguracja Nginx dla Next.js
upstream nextjs {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    include /etc/nginx/snippets/ssl-params.conf;
    include /etc/nginx/snippets/security-headers.conf;

    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    # Next.js static assets - agresywne cache
    location /_next/static/ {
        proxy_pass http://nextjs;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # Next.js image optimization
    location /_next/image {
        proxy_pass http://nextjs;
        proxy_cache PROXYCACHE;
        proxy_cache_valid 200 24h;
        proxy_cache_key "$request_uri";
    }

    # Publiczne assety
    location /public/ {
        alias /var/www/app/public/;
        expires 30d;
        add_header Cache-Control "public";
        access_log off;
    }

    # Wszystko inne do Next.js
    location / {
        proxy_pass http://nextjs;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Apache dla PHP / Laravel#

# Produkcyjna konfiguracja Apache dla Laravel
<VirtualHost *:443>
    ServerName laravel.example.com
    DocumentRoot /var/www/laravel/public

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/laravel.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/laravel.example.com/privkey.pem

    # PHP-FPM
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>

    # Katalog główny
    <Directory /var/www/laravel/public>
        AllowOverride All
        Require all granted
        Options -Indexes +FollowSymLinks -MultiViews
    </Directory>

    # Blokowanie dostępu do wrażliwych katalogów
    <DirectoryMatch "^/var/www/laravel/(storage|vendor|bootstrap/cache)">
        Require all denied
    </DirectoryMatch>

    # Blokowanie plików .env
    <FilesMatch "^\.env">
        Require all denied
    </FilesMatch>

    # Nagłówki bezpieczeństwa
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

    # Kompresja
    SetOutputFilter DEFLATE
    AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json

    # Logowanie
    ErrorLog ${APACHE_LOG_DIR}/laravel-error.log
    CustomLog ${APACHE_LOG_DIR}/laravel-access.log combined
</VirtualHost>

Kiedy wybrać Nginx, a kiedy Apache?#

Wybierz Nginx gdy:#

  • Budujesz aplikację Node.js / Next.js - Nginx jest naturalnym reverse proxy dla aplikacji Node
  • Potrzebujesz obsługi wielu jednoczesnych połączeń - WebSocket, SSE, long polling
  • Serwujesz dużo plików statycznych - Nginx jest znacznie wydajniejszy
  • Load balancing - wbudowane algorytmy load balancingu są elastyczne i wydajne
  • Mikroserwisy - jako API gateway z routingiem do wielu backendów
  • Ograniczone zasoby - mniejsze zużycie RAM i CPU
  • Konteneryzacja (Docker/K8s) - mniejszy footprint, szybszy start

Wybierz Apache gdy:#

  • Hosting współdzielony - .htaccess pozwala na konfigurację per-katalog bez restartu serwera
  • Aplikacje PHP/Laravel - natywna integracja z mod_php (choć PHP-FPM jest zalecany)
  • Potrzebujesz dynamicznej konfiguracji - .htaccess umożliwia zmiany bez restartu
  • Złożone reguły rewrite - mod_rewrite jest bardziej rozbudowany
  • Wiele stron na jednym serwerze - izolacja konfiguracji przez .htaccess
  • Istniejąca infrastruktura - migracja z Apache na Nginx nie zawsze jest opłacalna

Rozwiązanie hybrydowe#

W wielu produkcyjnych środowiskach najlepszym rozwiązaniem jest kombinacja obu serwerów:

Klient → Nginx (reverse proxy, SSL, cache, static files)
           ├── Node.js / Next.js (port 3000)
           ├── Apache + PHP-FPM (port 8080)
           └── Inne mikroserwisy

Nginx pełni rolę front-end proxy, obsługując SSL termination, kompresję, cache i pliki statyczne, podczas gdy Apache obsługuje aplikacje PHP z pełną mocą .htaccess i mod_rewrite.

Podsumowanie#

Zarówno Nginx, jak i Apache są dojrzałymi, niezawodnymi serwerami WWW. Nginx dominuje w scenariuszach wymagających wysokiej wydajności, dużej liczby połączeń i pracy jako reverse proxy. Apache oferuje większą elastyczność konfiguracji i jest tradycyjnym wyborem dla aplikacji PHP. W nowoczesnych architekturach, Nginx jako front-end proxy z Apache jako backend dla PHP stanowi sprawdzone rozwiązanie łączące zalety obu serwerów.


Potrzebujesz profesjonalnej konfiguracji serwerów WWW? Zespół MDS Software Solutions Group specjalizuje się w optymalizacji infrastruktury webowej. Od konfiguracji Nginx i Apache, przez wdrożenia CI/CD, po skalowanie aplikacji w chmurze - pomożemy Ci zbudować wydajną i bezpieczną infrastrukturę. Skontaktuj się z nami, aby omówić Twój projekt.

Autor
MDS Software Solutions Group

Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.

Nginx vs Apache - Konfiguracja produkcyjna serwerów WWW | MDS Software Solutions Group | MDS Software Solutions Group