Brandvägg och grundhärdning av en VPS
De första stegen på en ny VPS med Ubuntu: egen användare, inloggning med ssh-nyckel, brandvägg som bara släpper in det som behövs, fail2ban, automatiska säkerhetsuppdateringar och hur Docker förhåller sig till brandväggen.
Vad ni får ut av det här
En ny server på internet får sina första inloggningsförsök inom minuter. Det är inte personligt. Det är program som provar vanliga användarnamn och lösenord mot varenda adress som finns, dygnet runt.
Den här guiden stänger de dörrar som sådana program letar efter. När ni är klara går det bara att logga in med en nyckel som finns på er dator, root kan inte logga in alls, brandväggen släpper bara in ssh och webbtrafik, och servern installerar säkerhetsrättningar själv. Ni vet också varför ett Docker-program ibland syns utifrån trots att brandväggen säger nej, och hur ni förhindrar det.
Guiden utgår från Ubuntu Server, som är det vanligaste valet hos VPS-leverantörerna. På Debian är stegen nästan identiska.
Det här behöver ni innan ni börjar
- En nyinstallerad VPS med Ubuntu och dess IP-adress.
- Inloggningsuppgifterna ni fick av leverantören, antingen ett root-lösenord eller en ssh-nyckel ni angav när servern skapades.
- En terminal på er egen dator. På Linux Mint heter den Terminal. På Windows 10 och 11 fungerar PowerShell, där ssh finns inbyggt.
- Tillgång till leverantörens webbkonsol, ifall ni låser ute er själva. Ta reda på var den finns innan ni börjar.
Ordningen i guiden spelar roll. Följ den. Den är lagd så att ni alltid har ett sätt att komma in innan ni stänger det gamla.
Steg 1: skapa en egen användare
Att arbeta som root innebär att varje felskrivet kommando körs med full behörighet. Skapa en vanlig användare som får använda sudo när det behövs. Logga in som root och kör (byt anna mot namnet på den som ska använda kontot):
adduser anna
usermod -aG sudo anna
adduser frågar efter ett lösenord. Välj ett långt och spara det i lösenordshanteraren; det används för sudo, inte för att logga in över nätet.
Steg 2: logga in med nyckel i stället för lösenord
En ssh-nyckel är ett par filer på er dator: en privat del som aldrig lämnar datorn och en publik del som ni lägger på servern. Den går inte att gissa sig till på samma sätt som ett lösenord.
Skapa nyckeln på er egen dator, inte på servern:
ssh-keygen -t ed25519
Tryck Enter för att spara på standardplatsen, och välj en lösenfras. Lösenfrasen skyddar nyckeln om datorn blir stulen.
Kopiera nu den publika delen till servern. På Linux Mint:
ssh-copy-id anna@203.0.113.10
På Windows finns inte ssh-copy-id. Gör samma sak i PowerShell med:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh anna@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Angav ni en nyckel redan när servern skapades ligger den hos root. Kopiera den till er nya användare genom att, inloggade som root, köra:
rsync --archive --chown=anna:anna ~/.ssh /home/anna
Prova nu att logga in som den nya användaren i ett nytt terminalfönster, och låt det gamla vara öppet:
ssh anna@203.0.113.10
Kommer ni in utan att servern frågar efter lösenord (bara nyckelns lösenfras) fungerar det. Kör sudo whoami och kontrollera att svaret är root. Gå inte vidare förrän båda de här sakerna fungerar.
Steg 3: stäng lösenordsinloggning och root
Nu när nyckeln fungerar kan lösenorden stängas av. Ubuntu läser extra inställningar från mappen /etc/ssh/sshd_config.d/, och det är enklast att lägga era ändringar i en egen fil där:
sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
Skriv in:
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
Spara med Ctrl+O och Enter, stäng med Ctrl+X.
Filnamnet börjar med 00 av ett skäl. För varje inställning använder ssh det första värdet den hittar, och filerna i mappen läses i bokstavsordning. Många VPS-leverantörer lägger en egen fil där, ofta 50-cloud-init.conf, och beroende på leverantören och på hur servern skapades kan den slå på lösenordsinloggning. Er fil måste läsas före den för att gälla, och med namnet 00- gör den det oavsett vad den andra filen innehåller.
Kontrollera att konfigurationen är giltig och starta om ssh:
sudo sshd -t
sudo systemctl restart ssh
Säger sshd -t ingenting är allt rätt. Öppna sedan ett tredje terminalfönster och logga in igen. Fungerar det kan ni stänga de gamla. Prova gärna också ssh root@203.0.113.10; den ska nu nekas.
Steg 4: brandväggen
Ubuntu har brandväggen ufw inbyggd, men avstängd. Ställ in den så att allt som kommer in stoppas utom det ni uttryckligen släpper in, och släpp in ssh innan ni slår på den. Gör ni i omvänd ordning stänger ni ute er själva.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
ufw varnar att befintliga ssh-anslutningar kan brytas. Svara y; ni har redan släppt in port 22.
Port 80 och 443 är för webbtrafik och behövs bara om servern ska visa webbsidor, till exempel via Caddy eller Traefik enligt guiden HTTPS med Caddy eller Traefik. Använder ni HTTP/3 lägger ni också till sudo ufw allow 443/udp.
Kontrollera resultatet:
sudo ufw status verbose
Ni ska se deny (incoming) som standard och tre eller fyra regler med ALLOW.
Många VPS-leverantörer har dessutom en brandvägg i kontrollpanelen, som ligger utanför servern. Använd den också om den finns, med samma portar. Den fungerar oberoende av allt som händer på servern, vilket blir viktigt i steg 7.
Steg 5: fail2ban
fail2ban läser inloggningsloggen och spärrar IP-adresser som misslyckas för många gånger. Med lösenordsinloggning avstängd kan ingen gissa sig in ändå, så fail2ban är mest ett sätt att minska bruset i loggarna och belastningen från automatiska försök. Det är fortfarande värt de fem minuterna.
sudo apt update
sudo apt install fail2ban
Ändra aldrig i filerna som slutar på .conf; de skrivs över vid uppdateringar. Lägg era inställningar i en fil som slutar på .local:
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
Det betyder: fem misslyckade försök inom tio minuter ger en timmes spärr. Något mer behövs inte i Ubuntu 24.04. Paketet har redan en egen fil, /etc/fail2ban/jail.d/defaults-debian.conf, som låter fail2ban läsa inloggningarna från systemets journal och slår på fängelset för ssh. Starta om och kontrollera:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Efter några timmar brukar listan över spärrade adresser ha börjat fyllas.
Steg 6: automatiska säkerhetsuppdateringar
Ubuntu Server har paketet unattended-upgrades installerat från början, och det installerar säkerhetsuppdateringar av sig självt. Kontrollera att det är påslaget:
cat /etc/apt/apt.conf.d/20auto-upgrades
Båda raderna ska sluta med "1". Gör de inte det, eller saknas filen, slå på det med:
sudo dpkg-reconfigure -plow unattended-upgrades
Som standard startar servern inte om sig själv efter en uppdatering. Vissa rättningar, främst av Linuxkärnan, gäller först efter en omstart. Titta efter filen /var/run/reboot-required när ni loggar in; finns den, starta om när det passar:
sudo reboot
Containrar med restart: unless-stopped startar igen av sig själva.
Steg 7: Docker går runt ufw
Det här är det som överraskar flest. När ett Docker-program publicerar en port, till exempel med ports: - "8080:80", gör Docker egna regler i Linux brandvägg som gäller före ufw:s regler. Porten blir åtkomlig från hela internet även om ufw status inte säger något om den. Dockers dokumentation beskriver själv beteendet och varnar för att publicerade portar som standard är öppna utåt.
Lösningen är att aldrig publicera en port mot omvärlden i onödan. Det finns två sätt.
Ingen port alls. Låt programmet ligga i samma Docker-nätverk som er reverse proxy (Caddy eller Traefik), och ta bort ports:-raden helt. Proxyn når programmet via tjänstens namn. Det är det bästa alternativet, och det är så guiden HTTPS med Caddy eller Traefik gör.
Bara på servern själv. Behöver ni porten på servern, till exempel för en proxy som körs utanför Docker, bind den till 127.0.0.1:
ports:
- "127.0.0.1:8080:80"
Då går porten bara att nå från servern. Äldre Docker-versioner än 28 hade ett hål här, där andra maskiner i samma lokala nät hos leverantören kunde nå även sådana portar, så håll Docker uppdaterat.
Kontrollera vilka portar som faktiskt lyssnar:
sudo ss -tlnp
Rader med 0.0.0.0: eller [::]: framför porten är öppna för alla. Rader med 127.0.0.1: är bara lokala. Prova sedan från er egen dator att nå en port som inte ska vara öppen, till exempel http://203.0.113.10:8080 i webbläsaren. Den ska inte svara.
Det här är också skälet till att brandväggen i leverantörens kontrollpanel är värd att använda. Den ligger utanför servern, och Docker kan inte ändra i den.
Så här vet ni att det funkade
ssh anna@ipfungerar med nyckeln, ochssh root@ipnekas.- Inloggning med lösenord nekas; ni kan prova med
ssh -o PubkeyAuthentication=no anna@ip. sudo ufw status verbosevisar deny som standard och bara de portar ni valt.sudo fail2ban-client status sshdvisar att fängelset är aktivt./etc/apt/apt.conf.d/20auto-upgradeshar två rader som slutar med"1".sudo ss -tlnpvisar inga Docker-portar på0.0.0.0utöver 80 och 443.
Vanliga fel
Utelåsta efter att ufw slogs på. Port 22 släpptes inte in först, eller ssh körs på en annan port. Logga in via leverantörens webbkonsol, kör sudo ufw allow 22/tcp, och prova igen.
"Permission denied (publickey)" efter steg 3. Nyckeln ligger inte hos den användare ni loggar in som, eller rättigheterna på ~/.ssh är för öppna. Logga in via webbkonsolen och kontrollera att /home/anna/.ssh/authorized_keys finns och ägs av anna.
Lösenordsinloggning fungerar fortfarande. En annan fil i /etc/ssh/sshd_config.d/ läses före er. Kör sudo sshd -T | grep -i passwordauthentication för att se vilket värde som faktiskt gäller, och byt namn på er fil så att den kommer först i bokstavsordning.
Ett program syns utifrån trots att ufw säger nej. Docker publicerar porten förbi ufw. Följ steg 7.
fail2ban spärrar er själva. Ni har skrivit fel lösenfras för många gånger. Vänta en timme, eller logga in via webbkonsolen och kör sudo fail2ban-client set sshd unbanip ER-IP-ADRESS.
Nästa steg
Servern är nu stängd för allt utom ssh och webbtrafik. Nästa steg är att se till att webbtrafiken är krypterad; guiden HTTPS med Caddy eller Traefik tar det.
Och ingen härdning ersätter en säkerhetskopia. Har ni inte satt upp Backup av en Docker-installation än är det dags nu, innan det finns något att förlora.