- add English language: config, menus, i18n, translations of all pages and posts - CV page renders the full resume in both languages with PDF download buttons - remove salary from CV PDFs, Telegram is @xs0k0lx everywhere, English level B2 - fix broken CV menu link, update theme submodule to Public/blowfish Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.9 KiB
title, date, draft, tags, summary
| title | date | draft | tags | summary | ||
|---|---|---|---|---|---|---|
| Initial Debian server setup | 2026-10-08T19:29:38+04:00 | false |
|
What to do with a Debian server after you buy it. An article for Linux users. |
Usually, after you buy a server, you get an email with an IP address and the root password. That's enough to connect to the server over ssh and start setting it up.
Just 3 steps:
- System update
- ssh setup
- nftables setup
Step zero is preparing ssh keys and a config for the server. To generate keys, run:
s0k0l:~$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/~/.ssh/id_ed25519):
It asks for a path for the key. You can just type the key name you want, and the pair will be created in the directory you ran the command from. The first time you can skip this and press Enter.
The second question is about a passphrase for the key. For ssh keys to production infrastructure it's better to set one. If you don't want to, just hit Enter.
Once the hosting provider brings the server up, copy the key over with
s0k0l:~$ ssh-copy-id root@<ip>
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 3 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@<ip>'s password:
Enter the password and you should see a success message suggesting you connect to the server, which is exactly what we'll do. But first add a new entry to ~/.ssh/config:
Host <name>
HostName <ip>
Port 22
IdentityFile ~/.ssh/id_ed25519
Now you can connect by name
ssh <name>
On the server, the first thing to do is run an update:
apt update && apt dist-upgrade -y && reboot
This updates the whole system and reboots it. Wait about 5 minutes and try to connect again.
Now we need to configure ssh so that nobody else can connect to the server on the default port or log in with a password. This is the most important part.
Open /etc/ssh/sshd_config for editing and find the parameters for the listen address and port.
Port 5872
AddressFamily inet
ListenAddress <ip>
#ListenAddress ::
Set them like this to strictly define how the server can be reached. We have an ipv4 address, so we can put it in the config explicitly. And the port should be changed to a random one in the range from 1024 to 65535. To check which ports are already taken, run:
ss -tlnup
If the port you want isn't in the output, it's free.
Next, find the following in the ssh config:
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
These settings disable ssh password authentication for all users, including root (PermitRootLogin).
After saving the config, restart the ssh service. Stay in your session until you've connected in parallel.
systemctl restart ssh
Open another terminal, fix the port in ~/.ssh/config and connect to the server. If the connection works, you can close the first terminal. SSH is done.
The last step is setting up nftables. It's the standard firewall in Debian, iptables is no longer needed.
The rules live in a single file, /etc/nftables.conf. The principle of a strict config is simple: all incoming traffic is denied, we allow only what is actually needed. Outgoing traffic stays open, otherwise updates and DNS will break.
Open /etc/nftables.conf and replace its contents entirely:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# let established connections through, drop garbage
ct state established,related accept
ct state invalid drop
# loopback interface
iif lo accept
# ping, but no more than 5 per second
ip protocol icmp icmp type echo-request limit rate 5/second accept
ip protocol icmp accept
meta l4proto ipv6-icmp accept
# ssh on our port, no more than 10 new connections per minute from one ip
tcp dport 5872 ct state new meter ssh_limit { ip saddr limit rate 10/minute } accept
# uncomment if the server will host a website
# tcp dport { 80, 443 } accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Change port 5872 to the one you set in sshd_config. This is the most important line in the config, a mistake here will lock you out of the server.
First, check the config for errors without applying anything:
nft -c -f /etc/nftables.conf
If the command printed nothing, the syntax is fine. Now a safety net in case we do lock ourselves out. Start a timer that will flush all rules in 2 minutes:
(sleep 120 && nft flush ruleset) &
And apply the config:
nft -f /etc/nftables.conf
Then, as with ssh, open another terminal and connect. If you got in, everything is fine, cancel the timer:
kill %1
If you didn't get in, just wait 2 minutes, the rules will be flushed automatically and you can calmly look for the mistake.
To see what is actually applied right now, run:
nft list ruleset
All that's left is to enable loading the rules at boot, otherwise after a reboot the server will be left without a firewall:
systemctl enable --now nftables
If the server will run Docker, keep in mind that it writes its own rules and can open container ports bypassing your config. That's a topic for a separate article.
That's it. The server is updated, ssh only lets you in with a key and on a non-standard port, and the firewall blocks everything we haven't explicitly allowed. From here you can start installing your services.
Disclaimer: the timer trick is needed because a firewall can drop even an already established connection. The example config has the line ct state established,related accept, which means "allow connections that are already established". So in theory there shouldn't be a disconnect.