Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Tani VPS od zera: nginx, Docker i certyfikat SSL krok po kroku

Pierwszy login, użytkownik z sudo, klucze SSH, zapora ufw, nginx, Docker i certyfikat Let's Encrypt. Komendy dla Ubuntu 24.04 i Debiana 12/13 z objaśnieniami.

Serwer i VPSzaktualizowano 7 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

VPS, na który logujesz się kluczem SSH jako zwykły użytkownik z sudo, z zaporą wpuszczającą tylko SSH i ruch WWW, z nginx serwującym stronę statyczną przez HTTPS i z Dockerem gotowym na narzędzia (analityka, monitoring, zbieranie błędów) – od czystego systemu, krok po kroku. Oprogramowanie nic nie kosztuje – płacisz tylko za VPS i domenę. Układ jest prosty: nginx na hoście, narzędzia w kontenerach, certyfikaty z Let’s Encrypt odnawiane automatycznie. Komendy działają na Ubuntu 24.04 LTS oraz na Debianie 12 i 13 – tam, gdzie systemy się różnią, piszemy to wprost. Na ten poradnik powołują się pozostałe poradniki o VPS-ie („serwer przygotowany jak w poradniku od zera”).

Czego potrzebujesz

  • VPS z Ubuntu 24.04 LTS albo Debianem 12 lub 13. Na stronę statyczną wystarczy 1 vCPU, 1 GB RAM i 10–20 GB dysku. Jeśli planujesz Docker z kilkoma narzędziami (Umami, Uptime Kuma, GlitchTip z bazą Postgres), weź co najmniej 2 GB RAM, a wygodniej 4 GB.
  • Domenę (w przykładach example.pl) z rekordem A (i AAAA, jeśli masz IPv6) wskazującym adres serwera.
  • Komputer z klientem OpenSSH (Linux, macOS, Windows 10/11 mają go wbudowanego).
  • Adres IP i dane pierwszego logowania z panelu dostawcy.

Pełne porównanie planów znajdziesz w rankingu VPS, a darmowe plany na stronie darmowe.

Krok 1. Pierwsze logowanie i aktualizacja systemu

Zaloguj się kontem podanym przez dostawcę – zwykle to root, na Ubuntu czasem ubuntu, na Debianie czasem debian:

bash
ssh root@203.0.113.10

Debian: obrazy Debiana często nie mają sudo. Jeśli sudo -V kończy się błędem „command not found”, zainstaluj je raz jako root (na Ubuntu ten krok pomiń):

bash
apt update && apt -y install sudo

Zaktualizuj system i ustaw strefę czasową (logi i zadania cron będą wtedy w polskim czasie):

bash
sudo apt update && sudo apt -y full-upgrade
sudo timedatectl set-timezone Europe/Warsaw

Jeśli aktualizacja zainstalowała nowe jądro, istnieje plik /var/run/reboot-required – wtedy wykonaj sudo reboot i zaloguj się ponownie.

Krok 2. Własny użytkownik z sudo

Praca na koncie root sprawia, że każda pomyłka ma pełne uprawnienia. Załóż zwykłego użytkownika i daj mu sudo:

bash
sudo adduser deploy
sudo usermod -aG sudo deploy

adduser zapyta o hasło – będzie potrzebne przy sudo, nie do logowania przez SSH. Grupa sudo istnieje w Ubuntu i w Debianie, więc te dwie komendy są takie same na obu systemach.

Krok 3. Klucz SSH

Na swoim komputerze wygeneruj klucz ed25519 (dokumentacja Ubuntu poleca ten algorytm) i skopiuj go na serwer:

bash
ssh-keygen -t ed25519 -C "vps-example"
ssh-copy-id deploy@203.0.113.10

ssh-copy-id loguje się na konto deploy hasłem. Jeśli dostawca wyłączył logowanie hasłem (część obrazów chmurowych tak ma) i klucz dodałeś w panelu przy zakładaniu serwera, skopiuj go z konta, na które się logujesz – z pierwszej sesji na serwerze:

bash
sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo install -m 600 -o deploy -g deploy ~/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys

Na Windowsie ssh-copy-id nie ma, więc dopisz klucz ręcznie z pierwszej sesji na serwerze:

bash
sudo -u deploy mkdir -p /home/deploy/.ssh
echo "ssh-ed25519 AAAA...twój-klucz... vps-example" | sudo -u deploy tee -a /home/deploy/.ssh/authorized_keys
sudo chmod 700 /home/deploy/.ssh && sudo chmod 600 /home/deploy/.ssh/authorized_keys

Teraz, w nowym oknie terminala, bez zamykania starej sesji, sprawdź logowanie:

bash
ssh -t deploy@203.0.113.10 "whoami && sudo -v && echo SUDO_OK"

Opcja -t przydziela terminal – bez niej sudo nie może zapytać o hasło i kończy się błędem. Podaj hasło z kroku 2. Dopiero gdy zobaczysz deploy i SUDO_OK, idź dalej. Stara sesja to Twoja droga powrotu, gdyby coś poszło źle.

Krok 4. Wyłączenie logowania hasłem i konta root

Ustawienia dopisz w osobnym pliku w sshd_config.d, żeby aktualizacja pakietu nie nadpisała Twoich zmian:

bash
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf >/dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF
sudo sshd -t && sudo systemctl restart ssh
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication)'

Nazwa zaczyna się od 00- celowo: sshd bierze pierwszą znalezioną wartość opcji, a wiele obrazów chmurowych ma plik 50-cloud-init.conf z PasswordAuthentication yes. Plik o nazwie 90-... zostałby po cichu zignorowany. Ostatnia komenda musi pokazać permitrootlogin no i passwordauthentication no.

Krok 5. Zapora ufw

Najpierw reguła dla SSH, potem włączenie zapory – w odwrotnej kolejności odetniesz sobie dostęp:

bash
sudo apt -y install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose

--force pomija pytanie o potwierdzenie, które ufw zadaje przy włączaniu przez SSH. Więcej o zaporze, fail2ban i automatycznych aktualizacjach jest w liście kontrolnej zabezpieczenia VPS.

Krok 6. nginx i strona statyczna

bash
sudo apt -y install nginx
sudo mkdir -p /var/www/example.pl
sudo chown -R deploy:deploy /var/www/example.pl
echo '<h1>Działa</h1>' > /var/www/example.pl/index.html

Utwórz plik /etc/nginx/sites-available/example.pl:

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

    root /var/www/example.pl;
    index index.html;
    charset utf-8;

    location / {
        try_files $uri $uri/ $uri/index.html =404;
    }
}

try_files z $uri/index.html obsługuje strony generowane w formacie katalogów (Astro, Hugo, Eleventy). Włącz konfigurację, usuń domyślną stronę i przeładuj nginx:

bash
sudo ln -s /etc/nginx/sites-available/example.pl /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
curl -sI http://example.pl | head -1

nginx -t sprawdza składnię przed przeładowaniem – rób to po każdej zmianie.

Krok 7. Certyfikat Let’s Encrypt

Certbot z wtyczką nginx sam dopisze konfigurację HTTPS i przekierowanie z HTTP:

bash
sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.pl -d www.example.pl
sudo certbot renew --dry-run
systemctl list-timers | grep certbot

Oficjalna instrukcja certbota poleca instalację przez snap (sudo snap install --classic certbot); pakiet z repozytorium Ubuntu albo Debiana też działa i instaluje timer systemd, który dwa razy dziennie sprawdza, czy trzeba odnowić certyfikat. Nie mieszaj obu instalacji. Certyfikat wildcard (*.example.pl) wymaga walidacji DNS-01 – to temat na osobny poradnik o certbocie.

Krok 8. Docker i Compose

Instalacja z oficjalnego repozytorium Dockera (wersja z repozytorium systemu bywa starsza i nie ma wtyczki compose). Docker ma osobne repozytoria dla Ubuntu i Debiana – poniższe komendy same wybierają właściwe na podstawie /etc/os-release:

bash
. /etc/os-release && echo "$ID ${UBUNTU_CODENAME:-$VERSION_CODENAME}"

Powinno się wyświetlić ubuntu noble, debian bookworm (Debian 12) albo debian trixie (Debian 13). Inny wynik oznacza system, którego ten poradnik nie obejmuje. Potem:

bash
. /etc/os-release
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/$ID
Suites: ${UBUNTU_CODENAME:-$VERSION_CODENAME}
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Ogranicz rozmiar logów kontenerów, bo domyślny sterownik json-file potrafi zapełnić dysk:

bash
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
EOF
sudo systemctl restart docker
sudo usermod -aG docker deploy

Wyloguj się i zaloguj ponownie, potem docker run --rm hello-world. Pamiętaj, że grupa docker daje w praktyce uprawnienia roota – dodawaj do niej tylko swoje konto.

Najczęstsze problemy

  • Nie da się zalogować po zmianie SSH. Wróć do starej sesji (dlatego jej nie zamykasz) albo do konsoli w panelu dostawcy. Sprawdź sudo sshd -T | grep -i pubkey i uprawnienia: ~/.ssh musi mieć 700, authorized_keys 600, właścicielem musi być użytkownik, nie root.
  • Logowanie hasłem nadal działa. Twój plik ma dalszą kolejność niż 50-cloud-init.conf. Zmień nazwę na 00-... i zrestartuj ssh.
  • Kontener z portem -p 3000:3000 jest dostępny z internetu mimo ufw. Docker dopisuje własne reguły iptables i według swojej dokumentacji opublikowane porty omijają ufw. Publikuj porty tylko na pętli zwrotnej: 127.0.0.1:3000:3000, a na zewnątrz wystawiaj usługę przez nginx.
  • Certbot kończy się błędem Timeout during connect albo unauthorized. Rekord A nie wskazuje jeszcze serwera (sprawdź dig +short example.pl) albo port 80 jest zamknięty w ufw lub w zaporze w panelu dostawcy. Walidacja HTTP-01 zawsze idzie przez port 80.
  • nginx zwraca 403. Brak index.html w katalogu albo nginx (użytkownik www-data) nie może czytać plików – sprawdź ls -l /var/www/example.pl i uprawnienia katalogów nadrzędnych.

Co dalej

Masz bazę, na której postawisz narzędzia: Umami zamiast Google Analytics, Uptime Kuma albo cały stack w jednym pliku compose. Zanim wrzucisz tam cokolwiek ważnego, przejdź listę kontrolną zabezpieczeń i ustaw kopie zapasowe.

Powiązane poradniki