SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-13

Nginx czy Caddy czy Traefik: co wybrać na VPS?

Porównanie Nginx, Caddy i Traefik pod kątem obsługi certyfikatów TLS, konfiguracji Docker oraz routingu. Sprawdź, które reverse proxy najlepiej obsłuży Twoje aplikacje webowe.

Nginx kontra Caddy kontra Traefik: krótka odpowiedź

Nginx, Caddy i Traefik pełnią tę samą funkcję jako reverse proxy: nasłuchują na porcie 443, odczytują nazwę hosta w każdym żądaniu i przekazują je do właściwej usługi na serwerze VPS. Każde z tych rozwiązań pozwoli umieścić cztery własne aplikacje za jednym publicznym adresem IP, a wszystkie są na tyle wydajne, że to same aplikacje będą stanowić wąskie gardło. Różnica polega na sposobie uzyskiwania certyfikatu TLS (transport layer security) oraz nakładzie pracy potrzebnym na konfigurację każdej kolejnej usługi. Kolejna różnica ujawnia się w momencie, gdy wymagane jest rozwiązanie wykraczające poza standardowe poradniki.

Wybierz Caddy, jeśli obsługa HTTPS ma odbywać się automatycznie, a uruchamiane usługi to standardowe aplikacje webowe. Wybierz Traefik, jeśli całość działa w środowisku Docker Compose, a nowe usługi są dodawane regularnie. Wybierz Nginx, jeśli już go używasz lub jeśli wymagane jest buforowanie odpowiedzi, certyfikaty klienckie, przekazywanie surowego ruchu TCP albo posiadasz rozbudowaną konfigurację, której nie chcesz przepisywać.

W jaki sposób każdy z nich uzyskuje certyfikat TLS?

Ta kwestia jest decydująca dla większości użytkowników, więc od niej należy zacząć. Wszystkie trzy rozwiązania kończą z tym samym certyfikatem od tego samego urzędu certyfikacji. Nakład pracy potrzebny do osiągnięcia tego celu jest jednak różny.

Caddy żąda certyfikatu, ponieważ zdefiniowano nazwę hosta. Wpisz app.example.com jako adres witryny, a Caddy zażąda certyfikatu przez ACME (automatic certificate management environment) od Let's Encrypt, w razie niepowodzenia przełączy się na ZeroSSL, obsłuży przekierowanie HTTP na HTTPS na porcie 80 i samodzielnie przeprowadzi odnowienie. Nie ma tu drugiego narzędzia ani timera do sprawdzania. Certyfikaty znajdują się w katalogu danych użytkownika caddy, /var/lib/caddy/.local/share/caddy w przypadku instalacji z pakietu, więc należy dodać tę ścieżkę do kopii zapasowych lub zaakceptować ponowne wydanie po przebudowie. Dla hosta, który nie jest publiczny, tls internal podpisuje certyfikat własnym lokalnym urzędem certyfikacji Caddy. Daje to ten sam efekt co tworzenie certyfikatu z podpisem własnym w systemie Ubuntu, przy czym odnawianie odbywa się automatycznie.

Nginx nie posiada klienta ACME. Certbot uzyskuje certyfikat, a jego wtyczka --nginx modyfikuje blok serwera, dodając nasłuchiwanie na porcie 443 oraz przekierowanie. Odnawianie jest uruchamiane przez timer systemd instalowany wraz z pakietem, więc istnieją dwa ruchome elementy i dwa miejsca do weryfikacji: systemctl list-timers | grep certbot pokazuje, że timer istnieje, a sudo certbot renew --dry-run potwierdza, że ścieżka odnawiania działa poprawnie. Instrukcja krok po kroku znajduje się w Certbot na Ubuntu 24.04 z Nginx, a to samo narzędzie obsługuje certyfikat typu wildcard poprzez wyzwanie DNS-01, gdy liczba subdomen jest zbyt duża, by wymieniać je pojedynczo.

Traefik posiada własnego klienta ACME. W konfiguracji statycznej definiuje się jeden resolver certyfikatów, z którego może korzystać każdy router. Cały stan, w tym klucz konta i certyfikaty, znajduje się w jednym pliku acme.json. Traefik odmawia użycia tego pliku, jeśli jest on czytelny dla kogokolwiek poza właścicielem, o czym informuje przed porzuceniem resolvera:

The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600

Należy zamontować katalog i pozwolić Traefikowi na samodzielne utworzenie pliku. Utworzenie go wcześniej za pomocą touch powoduje dziedziczenie ustawień umask, co jest najczęstszą przyczyną wystąpienia wspomnianego błędu.

Jedna zasada dotyczy wszystkich trzech rozwiązań. Wyzwanie HTTP-01 wymaga, aby port 80 był dostępny z Internetu, ponieważ urząd certyfikacji łączy się z nim zwrotnie. Otwarcie tylko portu 443 spowoduje niepowodzenie wydania certyfikatu, co w komunikatach błędów może być mylnie interpretowane jako problem z DNS.

To samo zadanie routingu dla dwóch aplikacji w trzech konfiguracjach

Zadanie: app.example.com kieruje ruch do usługi na 127.0.0.1:8080, files.example.com kieruje do usługi na 127.0.0.1:8081, obie przez HTTPS. Poniżej przedstawiono pełną konfigurację dla każdego proxy, aby różnica w stopniu złożoności była widoczna, a nie tylko deklarowana.

Nginx

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

Następnie należy utworzyć dowiązanie, przetestować konfigurację, przeładować usługę i dodać certyfikat.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

nginx -t z parametrami syntax is ok oraz test is successful to polecenie sprawdzające, które należy wykonać przed każdym przeładowaniem. Druga aplikacja to ten sam blok z inną nazwą hosta i portem. Linie proxy_set_header nie są dekoracją: gdy proxy_pass wskazuje adres, nginx domyślnie przesyła Host: 127.0.0.1:8080 do backendu, więc aplikacja budująca bezwzględne adresy URL na podstawie nagłówka Host przekieruje użytkowników na localhost.

Caddy

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

files.example.com {
	reverse_proxy 127.0.0.1:8081
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

To cały plik. reverse_proxy samodzielnie ustawia X-Forwarded-For, X-Forwarded-Proto oraz X-Forwarded-Host i domyślnie ignoruje wartości przesłane przez klienta w tych nagłówkach, dzięki czemu żądanie nie może wprowadzić backendu w błąd co do źródła pochodzenia. Certyfikaty, przekierowanie z portu 80 oraz odnawianie wynikają bezpośrednio z dwóch adresów witryn. Żadne inne wpisy w pliku nie są do tego wymagane.

Traefik

Traefik wymaga konfiguracji statycznej przed rozpoczęciem routingu. Jako usługa Compose, z obrazem aktualnym na sierpień 2026:

services:
  traefik:
    image: traefik:v3.7
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

Każda aplikacja posiada własną konfigurację routingu w etykietach, wewnątrz własnego pliku compose:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

loadbalancer.server.port to port wewnątrz kontenera, a nie port opublikowany, ponieważ Traefik łączy się z kontenerem poprzez współdzieloną sieć Docker. Aplikacja nie wymaga żadnej linii ports: i to jest główna zaleta: opublikowany jest tylko Traefik. Pełna konfiguracja, w tym współdzielona sieć i middleware przekierowujący, znajduje się w routingu wielu aplikacji za pomocą Traefik i Docker Compose.

Jaki jest koszt konfiguracji każdej dodatkowej aplikacji?

ChartNon-blank config lines for the same two-app routing job
The data behind this chart
[
  {
    "tool": "Nginx",
    "proxy_setup_lines": 0,
    "lines_per_app": 11
  },
  {
    "tool": "Caddy",
    "proxy_setup_lines": 0,
    "lines_per_app": 3
  },
  {
    "tool": "Traefik",
    "proxy_setup_lines": 17,
    "lines_per_app": 5
  }
]

Liczba ta wynika z powyższych bloków. Blok serwera Nginx zajmuje 11 niepustych linii, które należy powielić dla każdej nazwy hosta. Blok witryny Caddy zajmuje 3 linii. Traefik wymaga 17 linii konfiguracji statycznej przed obsłużeniem pierwszego żądania, a następnie 5 etykiet na każdą aplikację.

Należy ocenić kompromisy, a nie szukać jednego zwycięzcy. Traefik generuje największy koszt początkowy przed dodaniem pierwszej aplikacji, ale najniższy koszt dla każdej kolejnej; oba podejścia zrównują się w okolicach trzeciej witryny. Poniżej tej liczby konfiguracja statyczna stanowi zbędny narzut. Powyżej niej etykiety zyskują przewagę, ponieważ routing znajduje się bezpośrednio przy obsługiwanej usłudze. Usunięcie usługi powoduje automatyczne usunięcie jej routingu, co jest słabą stroną centralnego pliku konfiguracyjnego, w którym często pozostają nieaktualne bloki serwera dla aplikacji nieistniejących od miesięcy.

Liczba linii jest również myląca na korzyść Nginx. Każdy z tych bloków wymaga utworzenia dowiązania symbolicznego, nginx -t, przeładowania konfiguracji oraz uruchomienia certbot, podczas gdy edycja Caddy wymaga tylko jednego przeładowania, a w przypadku Traefik nie jest wymagane żadne polecenie. Wszystkie trzy rozwiązania przeładowują konfigurację bez przerywania aktywnych połączeń. Różnica polega na liczbie odrębnych kroków, o których trzeba pamiętać o pierwszej w nocy.

Które rozwiązanie posiada wiedzę o kontenerach?

Traefik monitoruje gniazdo Docker i tworzy routery na podstawie etykiet kontenerów w momencie ich uruchamiania i zatrzymywania. Żadne inne rozwiązanie tutaj tego nie robi. Zarówno Nginx, jak i Caddy wymagają edycji konfiguracji oraz przeładowania po pojawieniu się nowego kontenera. Wymagają również adresu, pod którym mogą uzyskać dostęp do usługi: portu opublikowanego na interfejsie loopback lub współdzielonej sieci Docker, do której podłączone jest proxy.

Ta funkcjonalność ma swoją cenę, którą należy jasno określić. Traefik odczytuje /var/run/docker.sock. Każdy, kto może komunikować się z tym gniazdem, jest w stanie uruchomić kontener z zamontowanym systemem plików hosta, co oznacza uprawnienia root na hoście. Montowanie go w trybie tylko do odczytu zmniejsza ryzyko, ale go nie eliminuje. Jeśli ma to znaczenie dla modelu zagrożeń, należy umieścić pomiędzy nimi socket proxy, które udostępnia tylko te punkty końcowe listy kontenerów, których wymaga Traefik.

Caddy może realizować wykrywanie oparte na etykietach za pomocą wtyczki społecznościowej, jednak wtyczki Caddy są kompilowane wewnątrz binarki. Oznacza to konieczność zbudowania własnego pliku binarnego lub własnego obrazu za pomocą xcaddy, co przenosi na użytkownika odpowiedzialność za utrzymanie tej kompilacji i jej aktualizacje. W przypadku trzech lub czterech usług edycja pliku Caddyfile jest mniej pracochłonna.

Websockety i strumieniowanie: co ulega awarii i dlaczego

Nginx wymaga dodatkowej konfiguracji. Połączenie WebSocket rozpoczyna się jako żądanie HTTP zawierające Upgrade: websocket, a Nginx nie przekazuje nagłówków typu hop-by-hop do backendu, dopóki nie zostanie do tego jawnie skonfigurowany.

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Następnie wewnątrz bloku location należy umieścić trzy linie, które muszą wystąpić łącznie:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

Ich pominięcie spowoduje, że konsola przeglądarki wyświetli WebSocket connection to 'wss://app.example.com/ws' failed, podczas gdy w logach backendu widoczne będzie zwykłe żądanie GET. Zmienna map jest niezbędna, ponieważ w przypadku użycia zakodowanego na sztywno Connection: upgrade, byłoby ono wysyłane przy każdym żądaniu, w tym przy zwykłych zapytaniach, które powinny zawierać close.

Kolejne dwa domyślne ustawienia Nginx mogą sprawiać problemy. Parametr proxy_read_timeout wynosi 60 sekund i ma zastosowanie do tunelu po wykonaniu upgrade'u, więc WebSocket bez ruchu przez minutę zostanie zamknięty przez proxy. Z kolei zdarzenia przesyłane przez serwer (Server-Sent Events) docierają z opóźnieniem lub w paczkach, dopóki dla danej lokalizacji nie zostanie ustawione proxy_buffering off;, ponieważ Nginx przetrzymuje odpowiedź w buforze, podczas gdy strona na nią oczekuje.

Caddy wykonuje upgrade i przełącza połączenie w tryb dwukierunkowego tunelu bez żadnych dodatkowych dyrektyw. Opróżnia również bufor natychmiast, gdy odpowiedź ma status text/event-stream lub nie posiada określonej długości, dzięki czemu strumieniowanie działa bez ingerencji. Traefik przekazuje żądania upgrade'u i nie buforuje odpowiedzi, chyba że użytkownik samodzielnie doda middleware buffering. Jeśli usługi obejmują czat, terminal webowy, podgląd logów w czasie rzeczywistym lub interaktywne panele, stanowi to istotną różnicę w nakładzie pracy potrzebnej na konfigurację i debugowanie.

Pełny blok serwera Nginx, w tym obsługa Websocketów i SSE
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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 3600s;
        proxy_buffering off;
    }
}

Dyrektywa map powinna znajdować się w kontekście http, a nie wewnątrz server, dlatego należy przechowywać ją w osobnym pliku w katalogu /etc/nginx/conf.d/. Opcję proxy_buffering należy wyłączać tylko dla lokalizacji strumieniujących, ponieważ buforowanie pozwala Nginxowi na wcześniejsze zwolnienie procesu roboczego backendu przy zwykłych odpowiedziach. Certbot nadpisuje ten blok podczas uruchomienia, dlatego po jego użyciu należy ponownie sprawdzić plik.

Co zrobić w przypadku nietypowych wymagań?

W takich sytuacjach Nginx zyskuje dzięki swojej elastyczności konfiguracyjnej.

  • Certyfikaty klienckie, znane również jako mTLS (mutual TLS), wymagają od klienta przedstawienia własnego certyfikatu. Nginx wymaga dyrektyw ssl_client_certificate /etc/ssl/ca.pem; oraz ssl_verify_client on; w bloku server. Caddy wymaga bloku client_auth wewnątrz tls. Etykiety Traefik nie pozwalają na bezpośrednią konfigurację tego mechanizmu: należy zdefiniować opcję TLS w dostawcy plików i wskazać ją w routerze za pomocą traefik.http.routers.app.tls.options=mtls@file. Model oparty wyłącznie na etykietach wymaga w tym przypadku wyjątku.
  • Duże pliki przesyłane na serwer. Nginx domyślnie ogranicza rozmiar treści żądania do 1 MB. Próba przesłania większego pliku kończy się błędem 413 Request Entity Too Large, a w dzienniku błędów pojawia się wpis client intended to send too large body. Należy zwiększyć wartość client_max_body_size. Caddy i Traefik domyślnie nie nakładają limitów na treść żądania, więc trafia ono bezpośrednio do aplikacji, która sama decyduje o dopuszczalnym rozmiarze.
  • Buforowanie odpowiedzi. Nginx posiada dojrzały moduł proxy_cache. Caddy wymaga skompilowania odpowiedniej wtyczki. Wersja open source Traefik nie posiada wbudowanej obsługi pamięci podręcznej HTTP, co często zaskakuje użytkowników zakładających, że każdy serwer proxy oferuje taką funkcjonalność.
  • Obsługa surowego ruchu TCP lub UDP, na przykład dla portu bazy danych lub serwera gier. Nginx wykorzystuje moduł stream. Traefik obsługuje routery TCP i UDP na własnych punktach wejścia (entrypoints). Caddy wymaga w tym celu dodatkowej wtyczki, co wiąże się z koniecznością przygotowania własnej kompilacji.
  • Serwer WWW działający za proxy. Jeśli usługą jest klasyczna aplikacja PHP, to stos LAMP na Ubuntu 24.04 zawiera już Apache. Umieszczenie proxy przed nim powoduje, że dwa komponenty mogą ustawiać nagłówki i modyfikować adresy URL. Należy zdecydować, który z nich zajmie się terminacją TLS, a drugi pozostawić w trybie zwykłego HTTP, powiązanego wyłącznie z interfejsem loopback.

Pułapka firewalla wynikająca z tego wyboru

Celem stosowania reverse proxy jest pozostawienie otwartych wyłącznie portów 80 oraz 443. Docker po cichu niweczy to założenie. Opublikowanie portu za pomocą -p 8080:80 powoduje zapisanie reguły DNAT w tablicy nat, a reguła ta jest przetwarzana przed regułami INPUT zarządzanymi przez ufw. W rezultacie ufw deny 8080 nie blokuje tego ruchu, a aplikacja staje się dostępna w publicznym Internecie, obok starannie skonfigurowanego proxy. Należy wiązać publikowane porty z interfejsem loopback za pomocą 127.0.0.1:8080:80 lub całkowicie zrezygnować z ports:, pozwalając proxy na komunikację z kontenerem poprzez Docker network, co zostało przedstawione w powyższym przykładzie Traefik. Mechanizm działania oraz sposób naprawy opisano w dlaczego opublikowane porty Dockera omijają ufw.

Należy przeprowadzić test z maszyny innej niż VPS, ponieważ sprawdzenie wykonane z poziomu samego serwera zawsze zakończy się powodzeniem:

curl --max-time 5 http://your.server.address:8080

Connection refused lub przekroczenie czasu oczekiwania (timeout) to pożądany rezultat. Odpowiedź HTTP oznacza, że aplikacja jest osiągalna z pominięciem proxy, a wszystkie powyższe konfiguracje są jedynie dekoracją.

Które proxy wybrać?

Głównie statyczne strony oraz jedna lub dwie aplikacje: Caddy. Automatyczne HTTPS eliminuje największy powtarzalny obowiązek. Konfiguracja pozostaje na tyle krótka, że mieści się na jednym ekranie, a statyczna strona to jedna linia root i jedna linia file_server wewnątrz tego samego bloku witryny. Ceną jest mniejsza baza gotowych rozwiązań, gdy wystąpi nietypowy problem.

Homelab oparty na docker-compose, który stale rozbudowujesz: Traefik. Powyżej trzeciej usługi etykiety (labels) wymagają mniej pracy niż edycja centralnego pliku, a usunięcie usługi powoduje automatyczne usunięcie jej trasy. Zaplanuj popołudnie na pierwszą konfigurację, ponieważ entrypoints, routers, services oraz middlewares to nowa terminologia. Literówka w etykiecie zazwyczaj skutkuje błędem 404 ze strony Traefik, a nie błędem uruchomienia, dlatego sprawdź docker logs traefik pod kątem błędów parsowania, zanim założysz, że aplikacja nie działa.

Istniejąca konfiguracja Nginx lub dowolne wymaganie z powyższej listy: Nginx. Posiada on gotowe rozwiązania dla buforowania odpowiedzi oraz certyfikatów klienckich, a niemal każdy poradnik zewnętrzny zakłada jego użycie. Ceną jest to, że certyfikaty oraz obsługa websocket wymagają ręcznej konfiguracji, zamiast być dostępne w standardzie.

Niezależnie od wyboru, obowiązuje jedna zasada. Dokładnie jeden proces nasłuchuje na publicznym interfejsie, a cała reszta nasłuchuje na interfejsie loopback lub w prywatnej sieci Docker.

FAQ

Który reverse proxy jest najlepszy dla kilku aplikacji Docker na jednym VPS?

W przypadku trzech lub czterech usług, które są okresowo dodawane, Traefik zwraca się z nawiązką, ponieważ każda aplikacja zawiera własne etykiety routingu i nie wymaga edycji centralnego pliku. Jeśli usługi są stabilne, a głównym celem jest wyeliminowanie problemów z HTTPS, Caddy wymaga mniej nauki i jest mniej podatny na awarie. Nginx należy wybrać, gdy jest już znany lub gdy wymagana jest funkcja, której brakuje pozostałym dwóm, na przykład buforowanie odpowiedzi lub zwykły listener TCP.

Czy Caddy naprawdę nie wymaga konfiguracji certyfikatów?

W typowym przypadku tak. Podanie publicznej nazwy hosta jako adresu strony stanowi całą konfigurację: Caddy żąda certyfikatu przez ACME, obsługuje przekierowanie z portu 80 i odnawia certyfikat przed wygaśnięciem. Dwa warunki muszą być jednak spełnione. Port 80 musi być osiągalny z Internetu dla wyzwania HTTP-01, a rekord DNS A lub AAAA nazwy hosta musi już wskazywać na VPS, ponieważ urząd certyfikujący rozwiązuje nazwę i łączy się z nią zwrotnie.

Czy mogę uruchomić Nginx i Traefik na tym samym VPS?

Nie na tych samych portach. Ten, który uruchomi się jako drugi, nie powiąże się z portem; Nginx zgłosi bind() to 0.0.0.0:443 failed (98: Address already in use), a Traefik zarejestruje podobny błąd powiązania i zakończy działanie. Należy uruchomić jeden proxy na portach 80 i 443, a całą resztę umieścić za nim. W przypadku migracji należy przenosić nazwy hostów pojedynczo: niech przedni proxy przekazuje ruch do starego na porcie lokalnym (loopback), dopóki ostatnia witryna nie zostanie przeniesiona.

Dlaczego moje połączenia websockets są zrywane po 60 sekundach za Nginx?

proxy_read_timeout domyślnie wynosi 60 sekund i dotyczy tunelu po zakończeniu aktualizacji (upgrade), więc połączenie bez ruchu przez minutę jest zamykane przez proxy, a nie przez aplikację. Należy zwiększyć tę wartość w danej lokalizacji za pomocą proxy_read_timeout 3600s; lub skonfigurować aplikację tak, aby wysyłała ramkę ping co 30 sekund. Caddy i Traefik nie zamykają bezczynnych, zaktualizowanych połączeń po minucie, dlatego ta sama aplikacja może wydawać się stabilna za nimi, a niestabilna za Nginx.