Jak zabezpieczyć serwer WWW na Linuksie: HTTPS, nagłówki bezpieczeństwa, uprawnienia i aktualizacje
Źródło: Pexels | Autor: Miguel Á. Padriñán
Rate this post

Nawigacja:

Mapa kroków i punkt startowy: co zabezpieczasz i jak to zmierzysz

Minimalna baza przed zmianami

Cel jest prosty: utwardzić serwer WWW na Linuksie bez przestojów i bez niespodzianek. Zanim cokolwiek zmienisz, zbierz krótki obraz stanu. To ułatwi szybkie cofnięcie zmian i jasną ocenę postępów.

  • Inwentaryzacja:
    • Domeny i subdomeny, które obsługuje serwer (produkcyjne i testowe).
    • Wirtualne hosty i stos: Nginx lub Apache, ewentualnie reverse proxy, PHP-FPM, Node.js, Tomcat.
    • Aktualne certyfikaty: ścieżki plików, terminy wygaśnięcia, czy jest automatyczne odnawianie.
    • Wersje pakietów: openssl, nginx/apache, php-fpm, systemd, kernel.
    • System plików aplikacji: katalog główny, prawa dostępu, właściciele, użytkownicy procesów.
  • Backup i plan powrotu:
    • Migawka lub kopia konfigów: /etc/nginx, /etc/apache2, /etc/letsencrypt, /etc/php, pliki unitów systemd.
    • Kopia bazy danych oraz uploadów (jeżeli aplikacja je przechowuje lokalnie).
    • Szybki test odtwarzania na serwerze staging lub w kontenerze.
    • Okno zmian: kiedy możesz przeładować usługi bez wpływu na użytkowników.

Kryteria sukcesu i narzędzia do pomiaru

Aby nie wpaść w pułapkę „wydaje się bezpieczniej”, przyjmij twarde kryteria gotowości i od razu przygotuj narzędzia do testów.

  • HTTPS:
    • Wymuszone przekierowanie HTTP → HTTPS dla wszystkich hostów.
    • Wynik A lub A+ w testach SSL Labs (min. TLS 1.2, preferowany TLS 1.3).
    • OCSP stapling aktywny i łańcuch certyfikatów kompletny.
  • Nagłówki bezpieczeństwa:
    • Mozilla Observatory na poziomie co najmniej B+, docelowo A- lub A.
    • Obecne: Strict-Transport-Security, Content-Security-Policy (lub Report-Only podczas wdrożenia), X-Content-Type-Options, Referrer-Policy, Permissions-Policy, X-Frame-Options (lub frame-ancestors w CSP).
  • Uprawnienia i separacja:
    • Brak plików/katalogów z uprawnieniami 777/775 zapisywalnych dla „others”.
    • Usługi www nie działają jako root (wyjątek: procesy master z dropem uprawnień).
  • Aktualizacje:
    • Automatyczne aktualizacje bezpieczeństwa włączone.
    • Plan okna serwisowego dla aktualizacji większych (kernel, OpenSSL, Nginx/Apache).

Kiedy wdrażać, a kiedy uważać

Nie każda zmiana jest neutralna dla aplikacji. Nowa polityka CSP może zablokować skrypty na froncie, a agresywne HSTS zamknie drogę do cofnięcia się na HTTP. Kilka wskazówek:

  • Wdrażaj w godzinach mniejszego ruchu i miej plan szybkiego powrotu.
  • CSP najpierw w trybie Report-Only, dopiero potem egzekwowanie.
  • HSTS preload tylko wtedy, gdy wszystkie subdomeny konsekwentnie wspierają HTTPS.
  • AppArmor/SELinux aktywuj etapami, z monitorowaniem naruszeń i wyjątków.

Włącz i wymuś HTTPS: certyfikaty, TLS i przekierowania

Kolejność działań

  1. Klient ACME:
    • Debian/Ubuntu: apt install certbot python3-certbot-nginx lub python3-certbot-apache.
    • RHEL/Alma/Rocky: dnf install certbot python3-certbot-nginx lub python3-certbot-apache.
    • Alternatywa bez root: acme.sh (lekki, bez ingerencji w systemowe pakiety).
  2. Pobierz certyfikat:
    • Nginx: certbot --nginx -d example.com -d www.example.com
    • Apache: certbot --apache -d example.com -d www.example.com
    • Gdy nie możesz modyfikować vhostów automatycznie: certbot certonly --webroot -w /var/www/example -d example.com
  3. Parametry TLS (TLS 1.2/1.3, silne szyfry, OCSP stapling) – przykład poniżej.
  4. Wymuś przekierowanie 80 → 443 na każdym vhoście.
  5. Test i bezpieczne przeładowanie:
    • Nginx: nginx -t && systemctl reload nginx
    • Apache: apachectl configtest && systemctl reload apache2 lub httpd (na RHEL-owatych).

Przykład: Nginx (redirect, TLS, stapling)

server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;

  # Zezwól na ACME zanim przekierujesz wszystko
  location ^~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; }
  location / { return 301 https://$host$request_uri; }
}

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

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

  # TLS 1.2/1.3 i zestawy szyfrów
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256';
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;  # ~400k sesji
  ssl_session_tickets off;

  # Krzywe ECDH i DH (jeśli używasz)
  ssl_ecdh_curve X25519:secp384r1;

  # OCSP stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 1.1.1.1 1.0.0.1 valid=300s;
  resolver_timeout 5s;

  # HSTS – włącz dopiero po testach HTTPS na wszystkich subdomenach
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  root /var/www/example;
  index index.html index.php;
}
Jak zabezpieczyć serwer WWW na Linuksie: HTTPS, nagłówki bezpieczeństwa, uprawnienia i aktualizacje
Źródło: Pexels | Autor: Miguel Á. Padriñán

Przykład: Apache (redirect, TLS)

<VirtualHost *:80>
  ServerName example.com
  ServerAlias www.example.com
  # Utrzymaj dostęp do ACME
  Alias /.well-known/acme-challenge/ /var/www/letsencrypt/.well-known/acme-challenge/
  <Directory "/var/www/letsencrypt/.well-known/acme-challenge/">Require all granted</Directory>

  RewriteEngine On
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

<IfModule mod_ssl.c>
<VirtualHost *:443>
  ServerName example.com
  ServerAlias www.example.com

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

  SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
  SSLCipherSuite TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
  SSLHonorCipherOrder on
  SSLCompression off
  SSLSessionTickets off

  # OCSP stapling
  SSLUseStapling on
  SSLStaplingResponderTimeout 5
  SSLStaplingReturnResponderErrors off

  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

  DocumentRoot /var/www/example
</VirtualHost>
</IfModule>

Kontrola i typowe ostrzeżenia

  • Sprawdź stapling: openssl s_client -connect example.com:443 -status -servername example.com </dev/null | grep -A1 'OCSP response'
  • Nie blokuj ACME: reguły 301 mogą przykryć /.well-known/acme-challenge/. Zawsze dodaj wyjątek, jak w przykładach.
  • HSTS preload tylko, gdy każdy host i subdomena trwale serwuje HTTPS. Wycofanie jest trudne (czeka się na aktualizacje list).

Ustaw nagłówki bezpieczeństwa bez ryzyka przerwy

Start w trybie raportowania (CSP Report-Only)

Najpierw obserwuj, dopiero potem egzekwuj. W praktyce:

  • Dodaj CSP w trybie Report-Only i zbieraj raporty przez 2–7 dni.
  • Jeśli UI korzysta z data: przy obrazach lub zewnętrznych CDN, dostosuj dyrektywy zanim włączysz egzekwowanie.
# Nginx (w bloku server/location)
add_header Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; report-uri /csp-report" always;

# Apache
Header set always Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; report-uri /csp-report"

Prosty scenariusz: po włączeniu CSP obrazki z data: przestały się ładować – dodaj img-src 'self' data:. Jeśli używasz zewnętrznego CDN dla JS, rozszerz script-src o jego domenę lub wprowadź nonce’y.

Minimalny zestaw nagłówków (po dopracowaniu CSP)

# Nginx
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;  # lub polegaj na frame-ancestors w CSP

# Apache
Header set always Content-Security-Policy "default-src 'self'; object

Dopnij konfigurację nagłówków (Apache i detale)

# Apache – zestaw po dopracowaniu CSP
Header set always Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'"

Header set always X-Content-Type-Options "nosniff"
Header set always Referrer-Policy "strict-origin-when-cross-origin"
Header set always Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Jeśli nie używasz frame-ancestors w CSP:
Header set always X-Frame-Options "SAMEORIGIN"

# Drobne twardnienia:
Header unset X-Powered-By
  • Kryterium: po włączeniu sprawdź w devtools (Network → Response Headers), że każdy zasób HTML niesie komplet nagłówków.
  • Uwaga: nie dubluj tych samych nagłówków w bloku server i w location – w razie konfliktu przeglądarka weźmie ostatnią wartość.

Izolacja origin: COOP/COEP/CORP – kiedy warto, a kiedy uważać

Jeśli aplikacja to SPA/PWA lub przetwarza dane z innych źródeł, rozważ dodatkowe nagłówki zwiększające izolację. Dają realne korzyści, ale potrafią odciąć cross-origin zasoby.

# Nginx
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-site" always;

# Apache
Header set always Cross-Origin-Opener-Policy "same-origin"
Header set always Cross-Origin-Embedder-Policy "require-corp"
Header set always Cross-Origin-Resource-Policy "same-site"
  • Stosuj etapowo: najpierw na stagingu lub tylko dla /app/ i zasobów kontrolowanych przez Ciebie.
  • Jeśli ładujesz zewnętrzne skrypty/iframe’y, przygotuj nagłówki Cross-Origin-Resource-Policy: cross-origin po ich stronie albo zmień politykę.

Bezpieczne ciasteczka: Secure, HttpOnly, SameSite

Najszybciej ustawisz to w aplikacji. Gdy nie masz wpływu na kod, użyj warstwy www.

# Nginx (współczesne wersje) – dla cookie sesyjnego
proxy_cookie_flags sessionid Secure HttpOnly SameSite=Strict;

# Apache – dopisz atrybuty do Set-Cookie
Header edit Set-Cookie "(?i)^((?:(?!;).)+)$" "$1; Secure; HttpOnly; SameSite=Strict"
  • Scenariusz: SSO przez domenę trzecią może wymagać SameSite=None; Secure. Zidentyfikuj te ciasteczka i nadpisuj selektywnie (np. po prefiksie).
  • Kontrola: w devtools → Application → Cookies sprawdź, czy każdy cookie przenoszący sesję ma Secure/HttpOnly.

Dodatkowe twardnienia serwera

# Nginx
server_tokens off;     # nie ujawniaj wersji
autoindex off;         # brak listingów katalogów

# Apache
ServerTokens Prod
ServerSignature Off
<Directory "/var/www/example">
  Options -Indexes
</Directory>
  • Po zmianach zrób szybki test: curl -I https://example.com/nieistniejacy – nagłówek „Server” nie powinien zdradzać pełnej wersji, a katalogi nie mogą się listować.

Ogranicz uprawnienia plików, kont i procesów

Krok 1: właścicielstwo i prawa w katalogu aplikacji

Cel: tylko proces aplikacji może pisać, www może czytać, „others” bez zapisu. Zacznij od inwentaryzacji.

# Załóżmy użytkownika 'webapp' i grupę 'www-data' (Debian/Ubuntu) lub 'nginx'/'apache'
chown -R webapp:www-data /var/www/example

# Katalogi 750, pliki 640 (lub 444 dla statycznych niezmiennych)
find /var/www/example -type d -exec chmod 750 {} ;
find /var/www/example -type f -exec chmod 640 {} ;

# Wyszukaj ryzykowne prawa
find /var/www/example -perm -o=w -ls
  • Jeśli deploy pisze do /var/www/example/storage lub uploads, daj prawa zapisu tylko tam (770) i zachowaj setgid na katalogu, by nowe pliki dziedziczyły grupę: chmod g+s uploads.

Krok 2: serwer www bez uprawnień roota w pracy

Proces master może startować jako root, ale workers muszą spaść do niskoprzywilejowego użytkownika.

# Nginx (globalnie w nginx.conf)
user www-data;

# Apache (domyślnie) – sprawdź:
grep -E 'User|Group' /etc/apache2/apache2.conf /etc/httpd/conf/httpd.conf
  • PHP-FPM: osobny pool per aplikacja, z własnym user/group. Ogranicz powierzchnię przez listen.mode i katalogi tymczasowe.
; /etc/php/8.2/fpm/pool.d/example.conf
[example]
user = webapp
group = www-data
listen = /run/php/php-fpm-example.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
php_admin_value[upload_tmp_dir] = /var/www/example/tmp
php_admin_value[sys_temp_dir] = /var/www/example/tmp

Krok 3: separacja vhostów i ograniczenia dostępu

  • Jedna usługa – jeden użytkownik systemowy. Oddzielne katalogi, osobne logi i pule FPM.
  • Ogranicz metody HTTP na ścieżkach administracyjnych. Prosty przykład:
# Nginx – tylko GET/HEAD/POST dla całej aplikacji
limit_except GET HEAD POST { deny all; }

# Apache – dla /admin/
<Location "/admin">
  <LimitExcept GET POST HEAD>
    Require all denied
  </LimitExcept>
</Location>
Jak zabezpieczyć serwer WWW na Linuksie: HTTPS, nagłówki bezpieczeństwa, uprawnienia i aktualizacje
Źródło: Pexels | Autor: Pixabay

Krok 4: MAC – AppArmor/SELinux bez szoku

Zacznij w trybie obserwacji, zbierz naruszenia, dopiero potem egzekwuj.

  • SELinux:
    • Sprawdź status: getenforce. Jeśli Permissive – zbierz logi: ausearch -m avc -ts recent.
    • Generuj wyjątki w pakiet: audit2allow -a -M web_exceptions && semodule -i web_exceptions.pp.
    • Przełącz na Enforcing po tygodniu bez nowych AVC w krytycznych ścieżkach.
  • AppArmor:
    • Tryb complain dla profilu www, analiza z aa-logprof, potem enforce.

Krok 5: logi, rotacja i szybka diagnostyka

# Sprawdź rotację
cat /etc/logrotate.d/nginx
cat /etc/logrotate.d/apache2

# Szybki healthcheck po deployu
curl -sI https://example.com | grep -E "strict-transport-security|content-security-policy|server:"
  • Alarm na błędy 5xx i skoki 499/408 – to zwykle pierwsze sygnały problemów z uprawnieniami lub limitem zasobów.

Aktualizacje bez przestojów i bez zaskoczeń

Automaty bezpieczeństwa – włącz, ale miej kontrolę

# Debian/Ubuntu
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
# Status i dziennik:
systemctl status unattended-upgrades
journalctl -u unattended-upgrades --since "24 hours ago"

# RHEL/Alma/Rocky
dnf install dnf-automatic
sed -i 's/^apply_updates = .*/apply_updates = yes/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timer
journalctl -u dnf-automatic.timer --since "24 hours ago"
  • Polityka: automaty tylko dla łatek bezpieczeństwa; większe skoki (kernel, OpenSSL, Nginx/Apache) – ręcznie w oknie serwisowym.

Procedura okna serwisowego i test „kanarka”

  1. Staging: odtwarzaj produkcję, test TLS/headers: SSL Labs, Mozilla Observatory, sanity curl.
  2. Produkcja – kanarek: wdrożenie na jednym węźle/hostingu z mniejszym ruchem, 15–30 min obserwacji logów.
  3. Rolling reload:
    • Nginx: nginx -t && systemctl reload nginx (bez zrywania połączeń).
    • Apache event: apachectl configtest && systemctl reload apache2.
  4. Kernel:
    • Jeśli dostępne – livepatch/kpatch. Gdy brak, zaplanuj reboot z komunikatem i checkiem usług po starcie.

Domknij HTTPS: przekierowania, HSTS i OCSP stapling

Wymuś 301 na HTTPS i kanoniczny host

Najpierw zredukuj warianty adresów. Jeden host, jeden protokół – reszta 301.

# Nginx – przekieruj http → https i www → bez www (przykład)
server {
  listen 80;
  listen [::]:80;
  server_name www.example.com example.com;
  return 301 https://example.com$request_uri;
}

# Docelowy vhost
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  # Reszta konfiguracji TLS niżej...
}
# Apache – VirtualHost na 80 → 301
<VirtualHost *:80>
  ServerName example.com
  ServerAlias www.example.com
  Redirect permanent / https://example.com/
</VirtualHost>
  • Kryterium: curl -I -L http://www.example.com/test powinno kończyć na https://example.com/test z kodem 200 i bez kolejnych łańcuchów przekierowań.

HSTS – ostrożnie z includeSubDomains i preload

Zacznij od krótkiego TTL, dopiero po tygodniu-dwóch zwiększ do roku. Preload dopiero, gdy wszystkie subdomeny działają na HTTPS.

# Nginx
add_header Strict-Transport-Security "max-age=2592000" always; # 30 dni na start
# docelowo:
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Apache
Header always set Strict-Transport-Security "max-age=2592000"
  • Kontrola: curl -sI https://example.com | grep -i strict-transport-security – nagłówek ma być na każdej odpowiedzi HTML i 3xx w HTTPS.
  • Ostrzeżenie: includeSubDomains; preload zablokuje dostęp do dowolnej subdomeny po HTTP. Jeśli masz np. stary panel iot.example.com na http – najpierw go zmigruj.
Jak zabezpieczyć serwer WWW na Linuksie: HTTPS, nagłówki bezpieczeństwa, uprawnienia i aktualizacje
Źródło: Pexels | Autor: Markus Spiske

OCSP stapling i porządek w łańcuchu certyfikatów

Bez poprawnego łańcucha przeglądarka lubią przedłużać handshake. Włącz stapling i wskaż systemowy store CA.

# Nginx
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 9.9.9.9 valid=300s ipv6=off;
resolver_timeout 5s;
# Debian/Ubuntu:
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
# RHEL/Alma/Rocky:
# ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;
# Apache
SSLUseStapling on
SSLStaplingCache "shmcb:/var/run/ocsp(128000)"
# Upewnij się, że dajesz pełny łańcuch:
# Apache 2.4.8+: SSLCertificateFile może wskazywać fullchain.pem
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
  • Kontrola: openssl s_client -connect example.com:443 -servername example.com -status </dev/null | grep -i "OCSP Response Status" → „successful”.
  • Typowy problem: brak łańcucha (fullchain). Jeśli widzisz „Verify return code: 21 (unable to verify the first certificate)”, podmień ścieżkę na fullchain.pem.

Minimalny profil TLS i HTTP/2

# Nginx (bezpieczne minimum)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:
             ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
             ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:50m;
# Apache
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite PROFILE=SYSTEM # lub wyraźna lista jak w Nginx (OpenSSL format)
# HTTP/2:
Protocols h2 http/1.1
  • Test: SSL Labs powinien dać min. A/A+, a HTTP/2 ma być aktywny (np. curl -I --http2 https://example.com).

Automatyzacja certyfikatów i alarm, zanim wygasną

# Certbot – szybki sanity
certbot renew --dry-run

# Timery systemd (Debian/Ubuntu)
systemctl list-timers 'certbot*'

# Prosty check wygasania (+14 dni) – cron/Ansible
if [ $(date -d "$(openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null 
| openssl x509 -noout -enddate | cut -d= -f2)" +%s) -lt $(date -d "+14 days" +%s) ]; then
  echo "Cert blisko wygaśnięcia: example.com" | mail -s "ALERT TLS" admin@example.com
fi
  • Jeśli stoisz za CDN, odśwież też certyfikat po stronie CDN (API/terraform), inaczej klienci dalej zobaczą stary łańcuch.

Firewall i limity: odetnij hałas, zanim trafi do aplikacji

nftables/ufw – tylko 22/80/443 i ruch zaufany

Najmniej zaskoczeń daje prosty, stanowy filtr. IPv4 i IPv6 w jednej tabeli.

nftables: minimalny zestaw reguł + limitowanie SYN

Najpierw prosty, stanowy filtr. Zdefiniuj zaufane adresy do SSH i utnij resztę szumu.

# /etc/nftables.conf
flush ruleset

table inet filter {
  sets {
    admin_ssh { type ipv4_addr; flags interval; elements = { 203.0.113.10, 198.51.100.0/24 } }
  }

  chains {
    input {
      type filter hook input priority 0;
      policy drop;

      ct state established,related accept
      iifname lo accept

      # ICMPv4/ICMPv6 niezbędne do diagnostyki i PMTU
      ip protocol icmp accept
      ip6 nexthdr icmpv6 accept

      # SSH tylko z zaufanych
      tcp dport 22 ip saddr @admin_ssh accept

      # HTTP/HTTPS dla wszystkich
      tcp dport {80,443} accept

      # Ogranicz flood nowych połączeń (SYN)
      tcp flags & (syn|rst) == syn limit rate 50/second accept

      # Loguj podejrzane (z umiarem)
      limit rate 5/second counter log prefix "DROP-IN " level warning
      counter drop
    }

    forward { type filter hook forward priority 0; policy drop; }
    output  { type filter hook output  priority 0; policy accept; }
  }
}
  • Włącz i zapisz: nft -f /etc/nftables.conf && systemctl enable --now nftables
  • Kontrola: nft list ruleset – reguły załadowane; z zewnątrz nmap -Pn -p 22,80,443 example.com – 22 otwarty tylko z zaufanych.
  • Ostrzeżenie: jeśli łączysz się przez VPN/zmienne IP – daj awaryjnie „okno” SSH (np. 1h) z całego świata lub awaryjną konsolę z panelu serwera.

ufw: szybka konfiguracja bez zaskoczeń

Gdy nie chcesz dotykać nftables bezpośrednio, użyj ufw – wystarczy na pojedynczy host.

# IPv6 włączone
sed -i 's/IPV6=.*/IPV6=yes/' /etc/default/ufw

ufw default deny incoming
ufw default allow outgoing

# HTTP/HTTPS dla wszystkich
ufw allow 80/tcp
ufw allow 443/tcp

# SSH tylko z zaufanych (powtórz dla wielu adresów)
ufw allow from 203.0.113.10 to any port 22 proto tcp

# Łagodny rate-limit na SSH
ufw limit 22/tcp

# Włącz i sprawdź
ufw enable
ufw status numbered
  • Bezpieczne uruchomienie zdalnie: najpierw dodaj regułę dla swojego IP, dopiero potem ufw enable.

Rate limiting: loginy, API i „złe sąsiedztwo”

Blokuj nadużycia jeszcze przed aplikacją. Najpierw na poziomie WWW, precyzyjniej niż firewall.

# Nginx – kolejka żądań per IP dla /login i /api
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=peripconn:10m;

server {
  ...
  location /login {
    limit_req zone=perip burst=20 nodelay;
    limit_conn peripconn 5;
    try_files $uri @app;
  }
  location /api/ {
    limit_req zone=perip burst=50 delay=20;
    limit_conn peripconn 20;
    try_files $uri @api;
  }
}
# Apache – mod_evasive (prosty throttle)
a2enmod evasive
cat >/etc/apache2/mods-available/evasive.conf <<'EOF'
<IfModule mod_evasive20.c>
  DOSHashTableSize    3097
  DOSPageCount        10
  DOSSiteCount        200
  DOSPageInterval     1
  DOSSiteInterval     1
  DOSBlockingPeriod   10
  DOSEmailNotify      admin@example.com
  DOSLogDir           "/var/log/mod_evasive"
</IfModule>
EOF
systemctl reload apache2
  • Kontrola: hey -z 10s -q 50 https://example.com/login – oczekuj 429 przy nadużyciu.
  • Wyjątki: whitelista IP CDN/monitoringu; dla bursty API lepiej podnieść burst i użyć delay.

Fail2ban: szybkie „odgryzienie” nachalnych skanerów

apt install fail2ban
# /etc/fail2ban/jail.d/www.conf
[nginx-404]
enabled = true
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 30
findtime = 600
bantime = 3600
action = nftables-multiport[name=nginx-404, port="http,https"]

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 50
findtime = 300
bantime = 1800
action = nftables-multiport[name=nginx-rl, port="http,https"]

systemctl enable --now fail2ban
fail2ban-client status

Proste filtry (umieść w /etc/fail2ban/filter.d/):

# nginx-404.conf
[Definition]
failregex = ^<HOST> - .* "(GET|POST) .*" 404

# nginx-limit-req.conf – 429 od limit_req
[Definition]
failregex = limiting requests, excess:.* by zone.* client: <HOST>
  • Ustaw ban na godziny, nie na wieczność – prawdziwi użytkownicy też potrafią „kliknąć za dużo”.

Nagłówki bezpieczeństwa: wdrażaj w trybie „report-only”, potem egzekwuj

Bezpieczne minimum, które nie psuje produkcji

# Nginx
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header X-Frame-Options "SAMEORIGIN" always; # lub zamiast tego CSP frame-ancestors
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Apache
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Cross-Origin-Resource-Policy "same-origin"
  • Kontrola: curl -sI https://example.com | egrep -i "content-type-options|referrer-policy|frame|permissions|resource-policy"

Najczęściej zadawane pyt