Przejdź do treści
Aktualny kupon:10% zniżkiUżyj w zamówieniu

Strona WWW na VPS: Nginx i certyfikat Let's Encrypt (HTTPS)

Zainstaluj Nginx, serwuj statyczną stronę lub skonfiguruj reverse proxy dla aplikacji (Node.js i inne) i uzyskaj bezpłatny certyfikat Let's Encrypt, który odnawia się automatycznie.

  • zaawansowany
  • ~25 min
  • Zaktualizowano 9.10.2026

Nginx to szybki serwer WWW. Użyjemy go do serwowania statycznej strony albo postawimy go przed aplikacją jako reverse proxy i włączymy HTTPS z certyfikatem Let's Encrypt, który odnawia się sam.

Czego potrzebujesz

  • VPS w StormHost z Ubuntu 24.04 lub Debianem 13
  • Domena skierowana na adres IP VPS (rekord A, opcjonalnie AAAA); zobacz Domena na VPS i Cloudflare
  • Połączenie SSH jako root (poradnik)
  • Około 25 minut

Jak to działa#

Przeglądarka łączy się z portem 80 (HTTP) lub 443 (HTTPS) Twojego VPS. Na podstawie domeny w żądaniu Nginx wybiera właściwy server block (konfigurację jednej strony) i albo wysyła plik z dysku (strona statyczna), albo przekazuje żądanie aplikacji działającej na innym porcie (reverse proxy).

Certyfikat HTTPS wystawia bezpłatnie Let's Encrypt. Program Certbot weryfikuje, że kontrolujesz domenę (Let's Encrypt łączy się z portem 80 i pobiera plik kontrolny), pobiera certyfikat, konfiguruje go w Nginx i potem sam go odnawia.

Wymagania wstępne: DNS i porty#

  1. Sprawdź, czy domena wskazuje na VPS. Na VPS uruchom:

    bash
    getent hosts example.cz

    Zobaczysz adres IP swojego VPS (z zakładki Sieć). Jeśli nie, popraw DNS i poczekaj na propagację zmiany.

  2. Otwórz porty 80 i 443:

    • Firewall w strefie klienta (jeśli używasz trybu Blokuj wszystko poza dozwolonym): reguła Przychodzący · Zezwól · TCP · port 80,443. Poradnik: Firewall w strefie klienta.
    • UFW na VPS (jeśli go używasz): ufw allow 80/tcp i ufw allow 443/tcp.

Cloudflare: certyfikat uzyskaj z szarą chmurką

Jeśli domeną zarządzasz w Cloudflare, na czas uzyskiwania certyfikatu zostaw rekordy ustawione na DNS only (szara chmurka). Gdy już go masz, możesz przełączyć na Proxied; zobacz Cloudflare i tryby SSL.

Instalacja Nginx#

Działa zarówno na Ubuntu 24.04, jak i na Debianie 13:

bash
apt update
apt -y install nginx
  • apt -y install nginx – instaluje Nginx. Usługa startuje od razu i jest włączona przy uruchomieniu VPS.

Kontrola: otwórz w przeglądarce http://<ADRES_IP_VPS>. Zamiast <ADRES_IP_VPS> wstaw adres IPv4 z zakładki Sieć (na przykład http://203.0.113.10). Zobaczysz stronę „Welcome to nginx!”.

Jak uporządkowana jest konfiguracja#

ŚcieżkaDo czego służy
/etc/nginx/nginx.confGłówna konfiguracja. Zwykle jej nie zmieniasz. Wczytuje pliki z poniższych katalogów.
/etc/nginx/sites-available/Konfiguracje poszczególnych stron (server blocks). Plik tutaj istnieje, ale nie jest aktywny.
/etc/nginx/sites-enabled/Dowiązania (symlinki) do plików z sites-available. Nginx wczytuje tylko to, co tu jest.
/etc/nginx/conf.d/Dodatkowa konfiguracja; wczytywane są wszystkie pliki *.conf.
/var/www/Zwykłe miejsce na pliki stron.
/var/log/nginx/Logi: access.log (żądania) i error.log (błędy).

Stronę włączasz więc, tworząc plik w sites-available i dowiązanie do niego w sites-enabled. Wyłączasz ją, usuwając dowiązanie.

Strona statyczna#

Strona statyczna to zestaw gotowych plików HTML, CSS, JavaScript i obrazów.

  1. Utwórz katalog strony i stronę testową:

    bash
    mkdir -p /var/www/example.cz/html
    echo '<h1>It works</h1>' > /var/www/example.cz/html/index.html
    • mkdir -p – tworzy katalog łącznie z nadrzędnymi.
    • echo '…' > …/index.html – zapisuje prostą stronę do index.html.

    Później prześlij do tego katalogu własne pliki strony przez SFTP (poradnik) albo w zakładce Pliki.

  2. Utwórz konfigurację strony:

    bash
    nano /etc/nginx/sites-available/example.cz
    /etc/nginx/sites-available/example.cznginx
    server {
        listen 80;
        listen [::]:80;
        server_name example.cz www.example.cz;
    
        root /var/www/example.cz/html;
        index index.html;
    
        location / {
            try_files $uri $uri/ =404;
        }
    }
DyrektywaCo robiKiedy zmieniać
listen 80;Nasłuchuje na porcie 80 dla IPv4.Nigdy (Certbot dodaje HTTPS).
listen [::]:80;To samo dla IPv6.Usuń, jeśli VPS nie ma IPv6.
server_name example.cz www.example.cz;Domeny, których dotyczy ten blok. Nginx wybiera po nich stronę.Wypisz swoje domeny oddzielone spacjami.
root /var/www/example.cz/html;Katalog z plikami strony.Jeśli pliki leżą gdzie indziej.
index index.html;Który plik wysłać, gdy URL kończy się na /.Dla stron PHP index.php index.html.
location / { … }Reguły dla wszystkich URL strony.–
try_files $uri $uri/ =404;Próbuje znaleźć plik, potem katalog; jeśli nie istnieje żadne, zwraca błąd 404.Dla aplikacji jednostronicowych (React, Vue) try_files $uri $uri/ /index.html;.
  1. Włącz stronę, wyłącz domyślną stronę powitalną, przetestuj konfigurację i ją wczytaj:

    bash
    ln -s /etc/nginx/sites-available/example.cz /etc/nginx/sites-enabled/
    rm /etc/nginx/sites-enabled/default
    nginx -t
    systemctl reload nginx
    • ln -s … /etc/nginx/sites-enabled/ – tworzy dowiązanie, więc Nginx zaczyna wczytywać stronę.
    • rm /etc/nginx/sites-enabled/default – wyłącza domyślną stronę (plik zostaje w sites-available).
    • nginx -t – sprawdza składnię wszystkich konfiguracji. Musi zakończyć się syntax is ok i test is successful.
    • systemctl reload nginx – wczytuje nową konfigurację bez zrywania połączeń.

Kontrola: http://example.cz pokazuje nagłówek „It works” ze strony testowej.

Reverse proxy dla aplikacji#

Aplikacja (Node.js, Python, Java, kontener Dockera) zwykle działa na własnym porcie, na przykład 3000. Stawiasz przed nią Nginx jako reverse proxy: przyjmuje żądania na porcie 80/443 i przekazuje je aplikacji. Korzyści: Nginx obsługuje HTTPS, aplikacja nie musi być widoczna z internetu, a na jednym VPS może działać kilka aplikacji pod różnymi domenami.

Niech aplikacja nasłuchuje tylko na 127.0.0.1

Skonfiguruj aplikację tak, aby nasłuchiwała na 127.0.0.1:3000, nie na 0.0.0.0:3000. Jest wtedy dostępna tylko przez Nginx i nikt nie ominie go, łącząc się bezpośrednio z portem. Z Dockerem publikuj port jako 127.0.0.1:3000:3000 (zobacz Docker i Docker Compose).

Konfiguracja (zamiast strony statycznej albo jako kolejny plik dla innej domeny, na przykład app.example.cz):

/etc/nginx/sites-available/app.example.cznginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name app.example.cz;

    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        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;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 300s;
    }
}
DyrektywaCo robiKiedy zmieniać
map $http_upgrade $connection_upgrade { … }Zmienna pomocnicza dla websocketów: gdy przeglądarka prosi o podniesienie połączenia, wysyłane jest upgrade, w przeciwnym razie close. Potrzebna tylko raz w całej konfiguracji (druga kopia sprawia, że nginx -t zgłasza duplikat).Nigdy.
client_max_body_size 20m;Maksymalny rozmiar przesyłanego żądania. Domyślnie 1 MB; większe przesyłki kończą się błędem 413.Zależnie od tego, co przyjmuje aplikacja.
proxy_pass http://127.0.0.1:3000;Dokąd przekazywane są żądania.Port Twojej aplikacji.
proxy_http_version 1.1;Rozmawia z aplikacją przez HTTP/1.1. Starsze wersje Nginx używają inaczej HTTP/1.0, który nie działa z websocketami.Nigdy.
proxy_set_header Host $host;Aplikacja widzi pierwotną domenę z przeglądarki.Nigdy.
proxy_set_header X-Real-IP …, X-Forwarded-For …Aplikacja widzi prawdziwy adres IP odwiedzającego zamiast 127.0.0.1.Nigdy.
proxy_set_header X-Forwarded-Proto $scheme;Aplikacja wie, że odwiedzający przyszedł przez HTTPS (ważne dla przekierowań i ciasteczek).Nigdy.
Upgrade, ConnectionPrzepuszczają żądania websocket (czat, aktualizacje na żywo, Socket.IO).Nigdy.
proxy_read_timeout 300s;Jak długo czekać na odpowiedź aplikacji. Domyślne 60 s zamyka bezczynne websockety.Zwiększ dla długotrwałych operacji.

Włączenie działa tak samo jak przy stronie statycznej:

bash
ln -s /etc/nginx/sites-available/app.example.cz /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Aby aplikacja działała stale i startowała automatycznie po restarcie VPS, skonfiguruj ją jako usługę: Aplikacja 24/7 z systemd.

HTTPS przez Certbot#

Instalacja Certbota#

bash
apt -y install certbot python3-certbot-nginx
  • certbot – klient Let's Encrypt.
  • python3-certbot-nginx – wtyczka, która potrafi zweryfikować domenę przez Nginx i sama edytować jego konfigurację.

Certbot ze snapa

Autorzy Certbota zalecają instalację przez snap, która ma zawsze najnowszą wersję. Pakiet dystrybucji (Ubuntu 24.04 ma Certbot 2.9, Debian 13 ma 4.0) działa tak samo w normalnym użyciu i aktualizuje się z resztą pakietów. Jeśli chcesz snapa, postępuj według certbot.eff.org/instructions (otwiera się w nowym oknie) i najpierw odinstaluj pakiety apt, aby nie mieć dwóch Certbotów.

Uzyskanie certyfikatu#

bash
certbot --nginx -d example.cz -d www.example.cz
  • --nginx – weryfikacja przez Nginx i automatyczna edycja jego konfiguracji.
  • -d example.cz -d www.example.cz – domeny, dla których chcesz certyfikat. Każda musi mieć rekord DNS wskazujący na VPS i być wymieniona w server_name jakiejś strony.

Przy pierwszym uruchomieniu Certbot zadaje te pytania:

PytanieCo odpowiedziećDlaczego
E-mail (Enter email address…)Twój adresLet's Encrypt wysyła tam ważne powiadomienia. Od 2025 nie wysyła już przypomnień o wygaśnięciu; odnawianie jest automatyczne.
Please read the Terms of Service… Do you agree?YBez zgody na warunki nie dostaniesz certyfikatu.
Would you be willing… to share your email address with the Electronic Frontier Foundation…Y lub NTylko dla newslettera EFF; nie wpływa na certyfikat.

Wynik: Successfully deployed certificate for example.cz to /etc/nginx/sites-enabled/example.cz i Congratulations! You have successfully enabled HTTPS….

Co Certbot zmienił w konfiguracji#

Otwórz konfigurację strony (nano /etc/nginx/sites-available/example.cz). Linie oznaczone # managed by Certbot dodał Certbot:

Dodana liniaCo robi
listen 443 ssl;Strona nasłuchuje także na porcie 443 z TLS.
listen [::]:443 ssl ipv6only=on;To samo dla IPv6. Certbot dodaje ją, gdy blok zawiera listen [::]:80. Opcję ipv6only=on (nasłuchuj na IPv6 tylko dla połączeń IPv6) dodaje tylko dla pierwszej strony na VPS; dla pozostałych linia jest bez niej.
ssl_certificate /etc/letsencrypt/live/example.cz/fullchain.pem;Certyfikat (łącznie z pośrednimi).
ssl_certificate_key /etc/letsencrypt/live/example.cz/privkey.pem;Klucz prywatny certyfikatu. Nigdy nikomu go nie dawaj.
include /etc/letsencrypt/options-ssl-nginx.conf;Zalecane przez Certbot ustawienia TLS (protokoły, szyfry).
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;Parametry wymiany kluczy.
nowy blok server z return 301 https://…Przekierowuje HTTP na HTTPS. Certbot dodaje go automatycznie.

Nie edytuj tych linii ręcznie, chyba że dokładnie wiesz dlaczego. Certbot opiera się na nich przy odnawianiu.

Automatyczne odnawianie#

Certyfikat Let's Encrypt jest ważny 90 dni (stan na październik 2026; Let's Encrypt stopniowo skraca ważność). Pakiet Certbota zainstalował timer, który dwa razy dziennie sprawdza certyfikaty i odnawia te, którym została mniej więcej jedna trzecia ważności. Wtyczka Nginx sama przeładowuje Nginx po odnowieniu.

Sprawdź, czy timer istnieje i czy odnawianie działa:

bash
systemctl list-timers | grep certbot
certbot renew --dry-run
  • systemctl list-timers | grep certbot – wypisuje linię z certbot.timer i czasem następnego uruchomienia.
  • certbot renew --dry-run – próbuje odnowienia na serwerze testowym bez dotykania prawdziwych certyfikatów. Musi zakończyć się Congratulations, all simulated renewals succeeded.

certbot certificates wypisuje Twoje certyfikaty i ich ważność.

Limity Let's Encrypt

Let's Encrypt wystawia najwyżej 5 certyfikatów dla tego samego zestawu domen w ciągu 7 dni, a po 5 nieudanych walidacjach domeny w ciągu godziny tymczasowo Cię blokuje. Podczas debugowania używaj --dry-run (serwer testowy), a prawdziwe polecenie uruchom dopiero, gdy test przejdzie.

Cloudflare i tryby SSL#

Jeśli strona działa przez proxy Cloudflare (pomarańczowa chmurka), szyfrowane są dwa połączenia: między odwiedzającym a Cloudflare oraz między Cloudflare a Twoim VPS. Drugim steruje tryb SSL/TLS w Cloudflare:

TrybPołączenie z Cloudflare do VPSZalecenie
OffBez szyfrowaniaNie używaj.
FlexibleNieszyfrowane HTTP na port 80Nie używaj. W połączeniu z przekierowaniem HTTPS w Nginx tworzy nieskończoną pętlę (ERR_TOO_MANY_REDIRECTS).
FullTen sam protokół co odwiedzający; przy HTTPS certyfikat VPS nie jest weryfikowanyTylko tymczasowo.
Full (strict)Ten sam protokół co odwiedzający; przy HTTPS certyfikat VPS jest weryfikowanyZalecany. Twój certyfikat Let's Encrypt jest ważny, więc działa.

Po przełączeniu na Proxied uruchom certbot renew --dry-run i sprawdź, czy odnawianie działa także przez proxy Cloudflare. Jeśli się nie powiedzie, upewnij się, że nie blokujesz portu 80 własnymi regułami w Cloudflare. Trwałe rozwiązanie dla zaawansowanych to weryfikacja domeny przez DNS wtyczką python3-certbot-dns-cloudflare (zobacz dokumentację Certbota (otwiera się w nowym oknie)).

Podstawowe zabezpieczenie#

  1. Ukryj wersję Nginx. Na Debianie 13 /etc/nginx/nginx.conf zawiera już domyślnie server_tokens off;. Na Ubuntu 24.04 otwórz plik (nano /etc/nginx/nginx.conf) i w bloku http usuń # przed # server_tokens off;. Nginx nie zdradza wtedy swojej wersji w nagłówkach ani na stronach błędów.

  2. Nagłówki bezpieczeństwa. Możesz dodać je do bloku server z portem 443:

    nginx
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    • X-Content-Type-Options nosniff – przeglądarka nie zgaduje typu pliku (ochrona przed zamaskowanymi skryptami).
    • X-Frame-Options SAMEORIGIN – strony nie da się osadzić w cudzej stronie w ramce (ochrona przed clickjackingiem).
    • Referrer-Policy strict-origin-when-cross-origin – inne strony dostają tylko domenę, nie pełny URL strony.
    • always – nagłówek jest wysyłany także z odpowiedziami błędów.
  3. Po każdej zmianie uruchom nginx -t i systemctl reload nginx.

Zastanów się, zanim włączysz HSTS

Nagłówek Strict-Transport-Security (HSTS) każe przeglądarkom używać dla domeny tylko HTTPS, nawet miesiące naprzód. Jeśli HTTPS potem przestanie działać (wygasły certyfikat, przenosiny domeny), strona się nie wczyta. Włącz go dopiero, gdy HTTPS działa niezawodnie.

Weryfikacja#

bash
curl -I http://example.cz
curl -I https://example.cz
  • curl -I – pobiera tylko nagłówki odpowiedzi.

Pierwsze polecenie zwraca HTTP/1.1 301 Moved Permanently z nagłówkiem Location: https://example.cz/, drugie HTTP/1.1 200 OK (lub HTTP/2 200). W przeglądarce zobaczysz kłódkę obok adresu.

Rozwiązywanie problemów#

nginx -t zgłasza błąd#

Wyjście zawiera plik i numer linii, na przykład unexpected "}" in /etc/nginx/sites-enabled/example.cz:12. Najczęściej brakuje średnika na końcu dyrektywy albo jest zbędny lub brakujący nawias klamrowy. Popraw linię i uruchom test ponownie. Nie restartuj Nginx z zepsutą konfiguracją; nie wystartowałby.

502 Bad Gateway#

Nginx działa, ale aplikacja za nim nie odpowiada. Sprawdź, czy aplikacja działa i nasłuchuje na porcie z proxy_pass:

bash
ss -tlnp | grep 3000
  • ss -tlnp – nasłuchujące porty TCP z nazwami programów.

Jeśli nic się nie wypisuje, aplikacja nie działa albo nasłuchuje gdzie indziej. Szczegóły są w /var/log/nginx/error.log (linia z connect() failed (111: Connection refused)).

504 Gateway Time-out#

Aplikacja odpowiedziała za późno. Zwiększ proxy_read_timeout albo ustal, dlaczego aplikacja jest wolna.

413 Request Entity Too Large#

Przesyłany plik jest większy niż client_max_body_size (domyślnie 1 MB). Zwiększ wartość w bloku server i przeładuj konfigurację.

Zamiast strony wyświetla się „Welcome to nginx!”#

Domyślna strona jest nadal aktywna albo domena w server_name nie zgadza się z adresem w przeglądarce. Usuń /etc/nginx/sites-enabled/default i sprawdź server_name.

Nginx nie startuje: Address already in use#

Port 80 lub 443 zajmuje inny program, zwykle Apache. Ustal który: ss -tlnp | grep -E ':80 |:443 '. Zatrzymaj i wyłącz Apache (systemctl disable --now apache2) albo go odinstaluj.

Certbot: Could not automatically find a matching server block#

Żadna strona nie ma w server_name domeny, dla której żądasz certyfikatu. Dodaj ją do server_name, uruchom nginx -t, systemctl reload nginx i ponownie Certbota.

Certbot: Timeout during connect (likely firewall problem) lub Connection refused#

Let's Encrypt nie mógł dotrzeć do portu 80. Sprawdź DNS (getent hosts example.cz), firewall w strefie klienta i UFW. Jeśli domena ma rekord AAAA, musi wskazywać na adres IPv6 tego VPS, a strona musi nasłuchiwać także na IPv6 (listen [::]:80;).

Cloudflare: błąd 521, 522, 525 lub 526#

  • 521 – VPS odrzucił połączenie: Nginx nie działa albo firewall blokuje port 443.
  • 522 – przekroczony czas połączenia: VPS jest nieosiągalny albo firewall odrzuca pakiety.
  • 525 – nie powiódł się handshake TLS: Nginx nie ma poprawnej konfiguracji HTTPS na porcie 443.
  • 526 – certyfikat na VPS jest nieprawidłowy lub wygasł, w trybie Full (strict). Sprawdź certbot certificates.

ERR_TOO_MANY_REDIRECTS#

Zwykle Cloudflare w trybie Flexible w połączeniu z przekierowaniem HTTPS. Przełącz Cloudflare na Full (strict).

Podsumowanie

  • Strony leżą w /etc/nginx/sites-available/; aktywne są tylko te z dowiązaniem w sites-enabled.
  • Po każdej zmianie: nginx -t i systemctl reload nginx.
  • Niech aplikacja nasłuchuje na 127.0.0.1, a udostępniaj ją przez Nginx z proxy_pass, łącznie z nagłówkami X-Forwarded-*.
  • Certyfikat uzyskaj przez certbot --nginx -d …; odnawia się sam, a sprawdzisz to przez certbot renew --dry-run.
  • Z Cloudflare używaj trybu Full (strict), nigdy Flexible.

Wyszukiwanie w poradnikach