Initial Linux server setup using Debian as an example
Greetings!

This time we will perform basic Linux server setup using the Debian 12 distribution as an example.

Preface

We continue the topic of Linux server administration. In previous articles we:

Today we will perform the initial setup of a Debian 12 server before putting it into further use.

This guide is relevant both for setting up virtual servers, created for example in VirtualBox locally or with hosting providers, such as a Virtual Private Server (VPS), and for classic bare-metal servers.

Initial setup of the Debian server

Input data used in this article:

VM server IP address192.168.56.102
New server hostnamer4ven-test
Local user name on the serverivan
New SSH port, different from the default one27244

In this article, I assume that you have root access to the server, since the initial setup will be performed under that account.

Open the console of our VM and the first thing we do is…

Changing the root user’s password

Run:

BASH
passwd
Click to expand and view more

and enter the new password twice:

Now, for convenience, we can connect to the server via SSH and continue the setup in the terminal:

BASH
ssh root@192.168.56.102
Click to expand and view more

Setting up automatic installation of security updates

BASH
sudo unattended-upgrades apt-listchanges

sudo dpkg-reconfigure --priority low unattended-upgrades

systemctl status unattended-upgrades
Click to expand and view more

Installing the necessary utilities

Now let’s update the package cache, update the packages themselves, and install some useful utilities for further work with the server:

BASH
apt update && apt upgrade -y

apt install -y sudo ufw zsh software-properties-common neovim curl wget git mc fzf
Click to expand and view more

Description of the packages being installed:

Package nameDescription
sudoutility for obtaining superuser privileges
ufwa convenient frontend for the native Linux firewall — iptables/nftables
zshinteractive command shell (there’s a separate article on setting it up)
software-properties-commonpackage for managing additional software repositories
neovimadvanced console text editor (there’s an article introducing Vim/Neovim)
curlutility for exchanging data between hosts on the internet using data transfer protocols
wgetconvenient utility for downloading content from the internet
gitversion control system
mcMidnightCommander — a two-pane console file manager
fzf“fuzzy” search utility

Setting up the network with systemd-networkd

⚠️If your network is already configured, you can skip this step.

For the sake of consistency I prefer to configure the network on Linux servers using systemd-networkd.

❗Before making any changes, make sure you have physical access to the server / access to the VM console from the hypervisor, so that you can promptly roll back the configuration if something goes wrong.

First we back up the current settings files as recommended in the Debian documentation:

BASH
mv -v /etc/network/interfaces{,.backup}
mv -v /etc/network/interfaces.d{,.backup}
Click to expand and view more

If you use netplan, back it up too:

BASH
mv -v /etc/netplan{,.backup}
Click to expand and view more

Now let’s open a new network settings file for editing:

BASH
nvim /etc/systemd/network/10-lan0.network
Click to expand and view more

💡The number 10 in the file name determines the order in which settings are read from files, if there are several (10, 20, etc).

For static addressing, fill the file with content roughly like this:

⚠️Replace the network parameters with your own. You can find them in the /etc/network/interfaces* files.

PLAINTEXT
[Match]
Name=eth0
   
[Network]
Description=Local network
Address=192.168.56.102/24
Gateway=192.168.56.1
Click to expand and view more

If you get your network parameters via DHCP, then the content would be roughly like this:

PLAINTEXT
[Match]
Name=eth0

[Network]
DHCP=ipv4
Click to expand and view more

systemd-networkd accepts wildcard characters. If you have several interfaces getting their settings via DHCP, you can fill the file like this:

PLAINTEXT
[Match]
Name=e*

[Network]
DHCP=ipv4
Click to expand and view more

💡For more details on all systemd-networkd parameters see here.

Apply the settings and enable autostart:

BASH
systemctl restart systemd-networkd

systemctl enable systemd-networkd

systemctl status systemd-networkd
Click to expand and view more

💡To manage the network use the standard commands systemctl start|stop|restart systemd-networkd.

systemd-networkd comes with a convenient utility, networkctl, which you can use to view and manage network connection information:

BASH
networkctl status
Click to expand and view more

You can also check the status the classic way:

BASH
ip address
Click to expand and view more

Let’s check internet access:

BASH
ping -c3 r4ven.me
Click to expand and view more

It’s highly recommended to restart the server to make sure that the network comes up automatically and correctly:

BASH
reboot
Click to expand and view more

systemd-networkd allows you to configure the network in Linux quite flexibly. Network routes, routing rules, DNS, NTP settings, and so on are set in the files in a similar way.

Setting up DNS with systemd-resolved

⚠️If your DNS is already configured, you can skip this step.

Many modern Linux distributions use one of the systemd modules — systemd-resolved — to manage DNS.

systemd-resolved is a Systemd component responsible for DNS operation. It manages queries, caches results, and supports various protocols.

Let’s install the package:

❗Also make sure you have access to the server console before making changes.

BASH
apt install systemd-resolved
Click to expand and view more

☝️According to Debian’s policy, installed daemon services are most often started right after installation with autostart enabled on system boot.

To edit the DNS parameters, open the configuration file:

BASH
nvim /etc/systemd/resolved.conf
Click to expand and view more

And in the Resolve block, change (or add) the DNS= directive with your own values:

PLAINTEXT
[Resolve]
DNS=8.8.8.8 1.1.1.1
Domains=~.
Click to expand and view more

☝️If you don’t specify the Domains=~. option, systemd-resolved may use the DNS servers from the settings of individual network interfaces, if the Domains=~. parameter is present in them. This option does not affect domain name queries that match a more specific search domain from the interface settings.

Let’s save and restart the service, enabling autostart just in case + clear the DNS cache:

💡The systemd-resolved package comes with a DNS settings management utility — resolvectl.

BASH
systemctl restart systemd-resolved

systemctl enable systemd-resolved

systemctl status systemd-resolved

resolvectl flush-caches
Click to expand and view more

Also, for compatibility with applications that don’t use library calls but access the DNS server directly, it’s recommended to create such a symlink (with a prior backup of the resolv.conf file):

BASH
mv /etc/resolv.conf{,.backup}

ln -sv /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Click to expand and view more

For a detailed look at the DNS configuration process using systemd-resolved, I recommend checking out this wiki article.

⚠️ From experience, in the case of systemd-resolved it’s preferable to restart the entire system, but this is not mandatory.

Let’s check that DNS works:

BASH
resolvectl status

resolvectl query r4ven.me

ping -c3 r4ven.me

nslookup r4ven.me
Click to expand and view more

💡To manage DNS use the standard commands systemctl start|stop|restart systemd-resolved.

systemd-resolved has flexible settings, including DNSSEC, DNS over TLS, Multicast DNS (mDNS), Link-Local Multicast Name Resolution (LLMNR), and others.

Read more about how the DNS subsystem works in Linux here and here.

Setting up NTP with systemd-timesyncd

For many services in Linux, time accuracy is important. To make sure the server always knows the exact time, it’s recommended to configure its synchronization with the nearest NTP server. For this purpose we’ll use the systemd-timesyncd module.

Let’s install it:

BASH
apt install systemd-timesyncd
Click to expand and view more

Open the service’s config file for editing:

BASH
nvim /etc/systemd/timesyncd.conf
Click to expand and view more

And fill it with content roughly like this:

⚠️If needed, replace the list of NTP servers with your own.

PLAINTEXT
[Time]
NTP=0.ru.pool.ntp.org 1.ru.pool.ntp.org 2.ru.pool.ntp.org 3.ru.pool.ntp.org
FallbackNTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org
RootDistanceMaxSec=5
PollIntervalMinSec=32
PollIntervalMaxSec=2048
Click to expand and view more

The NTP server used will be determined according to the following rules:

For a more detailed description of how systemd-timesyncd works, check out this wiki article.

Let’s restart the service, add it to autostart, and check the status:

BASH
systemctl restart systemd-timesyncd

systemctl enable systemd-timesyncd

systemctl status systemd-timesyncd
Click to expand and view more

Systemd comes with a convenient management utility, timedatectl. You can check the service status with it like this:

BASH
timedatectl status

timedatectl timesync-status
Click to expand and view more

💡To manage systemd-timesyncd use the standard commands systemctl start|stop|restart systemd-timesyncd.

systemd-timesyncd is a convenient and easy-to-use NTP client that lets you quickly set up time synchronization over the network.

Adding a new user

⚠️ If you already have an existing local user, you can skip this step.

If, when installing Debian 12 or setting up the VM, for example in the case of a VPS, you didn’t create a new system user, let’s create one:

BASH
useradd -m -G sudo -s /bin/bash ivan

passwd ivan
Click to expand and view more

Where in the first command:

And with the passwd command we set a password for the new user. By default, new users don’t have one/it’s disabled.

Setting up SSH on the client host

For security reasons, it’s recommended not to work in Linux-based systems directly as the root user. Connecting under this user to remote servers is usually disabled. That’s what we’ll do, but a bit later, and for now let’s set up a connection with SSH key authorization on behalf of the user we created earlier.

Note that the steps in this section are performed on the user’s system, i.e. not on the server.

A few words about the type of keys to generate. Keys are generated using the ssh-keygen utility, which by default creates keys of the RSA type. This is a rather old cryptographic algorithm, created back in 1977, but still very popular, as it’s supported by most systems. You can read more about it on wikipedia.

The developers of the SSH protocol themselves recommend using keys of a more modern encryption algorithm — EdDSA, namely ed25519. This algorithm is based on elliptic curves. More details also on wiki. It is claimed to be more secure and better optimized for speed. That’s the one we’ll use.

So, on our desktop system let’s open a terminal and generate new keys (if you haven’t done this before):

BASH
test -e ~/.ssh || mkdir -v $HOME/.ssh/ && chmod 700 ~/.ssh

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
Click to expand and view more

With the command test -e ~/.ssh || mkdir $HOME/.ssh/ && chmod 700 ~/.ssh we checked for the presence of the SSH files directory, and if it doesn’t exist — we create it and set the correct permissions.

More about the redirection mechanism and command execution control operators can be read here: Command execution control: the “&&”, “||”, “;” and “&” operators.

During generation you’ll be asked for a passphrase to encrypt the key file itself. If you set one, each time you authenticate with this key the system will ask you for a password to decrypt the key file.

Ideally, you should set a password, but then the point of “passwordless” authorization on remote systems is lost. It all depends on your level of paranoia and where you’ll be connecting from. If these are your personal projects, for convenience you can skip setting a password. In this example I won’t set a password. To do this, press Enter twice at the key passphrase prompt.

There are also alternative solutions that let you encrypt the key file without entering a password every time to decrypt it — using the ssh-agent utility. But that’s a topic for a separate note)

After generation, two files will appear in the ~/.ssh directory:

Let’s check:

BASH
ls -l ~/.ssh/id_ed*
Click to expand and view more

Now let’s copy our public SSH key to log in to our server using a special command:

BASH
ssh-copy-id -i $HOME/.ssh/id_ed25519.pub ivan@192.168.56.102
Click to expand and view more

Don’t forget to substitute your own username and server IP address.

When running this, the system will ask for our user’s password on the server.

After the command finishes, a file ~/.ssh/authorized_keys will appear in the user’s home directory on our server, containing our public key from the client machine. We can check this:

BASH
cat ~/.ssh/id_ed25519.pub
Click to expand and view more
BASH
cat /home/ivan/.ssh/authorized_keys
Click to expand and view more

Essentially, we could have manually added this key/set of characters to the ~/.ssh/authorized_keys file, but the utility does everything itself + sets the correct permissions on the files.

Note that permissions on sensitive SSH files must be set exclusively for the file owner. Namely:

  • 700 for directories, including ~/.ssh
  • 600 for files, including ~/.ssh/authorized_keys

Otherwise the system simply won’t let you use them.

Now let’s try connecting to the server using the SSH key:

BASH
ssh ivan@192.168.56.102
Click to expand and view more

As we can see, the password is no longer requested.

Now, having successfully connected to the server under the new user, let’s continue the setup.

Before continuing, I’d like to highlight one more nuance. For the sake of greater security and to prevent unwanted consequences — it’s recommended to work in the Linux console under a regular user account, but one that has the ability to run commands as the root superuser.

In other words: connecting to the system, performing typical tasks that don’t require privileges, etc. should be done under a regular user. And when it’s necessary to carry out more serious manipulations, for example making some changes to the system settings — use the sudo command, without switching to the root account.

This way overall security is increased and the chance of accidentally breaking something is reduced. Of course, this can sometimes be inconvenient, and nothing stops you from simply switching to root. Just remember that what was said above is a recommendation that it’s simply advisable to follow.

Setting the default text editor

Readers of my previous articles already know that I prefer Vim/Neovim as a console text editor. If you’re interested in learning what this is and why I chose it, please go ahead and read . By the way, my Neovim config, as it gets configured further, will be updated in the repo on GitHub.

To set the default editor in the server’s console, run the command:

BASH
echo 'SELECTED_EDITOR="/usr/bin/nvim"' > ~/.selected_editor
Click to expand and view more

You can also create such a file for root, so that the settings are the same everywhere:

BASH
echo 'SELECTED_EDITOR="/usr/bin/nvim"' | sudo tee ~/.selected_editor
Click to expand and view more

You probably noticed that for performing the same operation, but for the root user, we used a slightly different command.

The thing is, if you run: sudo echo 'SELECTED_EDITOR="/usr/bin/nvim"' > /root/.selected_editor — we’ll get an error. In the case of the second command — there won’t be an error:

The peculiarity is that although the echo command is executed via sudo, the output redirection, intercepted by the > operator, is still executed as the regular user, so we get Permission denied.

In the case of the second command, it’s the opposite: first we output the desired text, and then, using the tee command run through sudo, we redirect the output on behalf of the superuser. In real practice you can often encounter this nuance of how the privilege mechanism works in Linux.

The tee command reads standard input and writes it to the specified file while also passing it to standard output.

You can read more about standard input/output in a separate article of mine: Linux command line, output and reading contents: echo, cat, less commands.

Disabling IPv6

I won’t go into detail about why you might want to disable IPv6 if it’s not being used. I’ll leave that for you to study on your own. I’ll just show how it’s done in Linux.

Open the system config for editing:

BASH
sudo nvim /etc/sysctl.conf
Click to expand and view more

And add the following lines to the end of the file:

BASH
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
Click to expand and view more

To reread the config, run:

BASH
sudo sysctl -p
Click to expand and view more

Setting up the UFW firewall

On every server I make sure to enable and configure a firewall. Linux-based OSes include the Netfilter module in the kernel, which is responsible for processing network packets. This module is managed by the iptables utility or its modern version, nftables. These utilities are low-level, and configuring them is a separate topic. For deb-based distributions there’s a simpler and more intuitive firewall management utility — ufw. In rpm distributions there’s a similar program — firewallcmd. But since we have Debian 12, I use ufw (installed it at the package installation step). Let’s do the basic network protection setup.

Let’s disable IPv6 in UFW. Open the ufw configuration file for editing:

BASH
sudo nvim /etc/default/ufw
Click to expand and view more

Change the line IPV6=yes to no:

BASH
IPV6=no
Click to expand and view more

Next let’s open external access to the SSH ports:

BASH
sudo ufw allow 22/tcp

sudo ufw allow 27244/tcp
Click to expand and view more

27244 is a non-standard port for SSH connections, which we’ll set below in the SSH server settings. It’s recommended to change the default ports of services where possible, since they’re usually scanned by bots.

We opened port 22 in order to avoid a broken connection and losing contact with the server. Be careful at this step!

Let’s enable and restart ufw:

BASH
sudo ufw enable && sudo systemctl restart ufw
Click to expand and view more

We’ll be warned that activating the firewall may drop the current SSH connection. But if you correctly added the rule to open port 22 TCP, everything should be fine.

If you’re still connected after running the command, congratulations, let’s continue.

You can view the existing rules in UFW like this:

BASH
sudo ufw status verbose
Click to expand and view more

Here we see that we have two ports “open” for external access. Pay attention to the 3rd line — this is the default packet handling policy

That is:

Blocking ICMP responses

Blocking ICMP with ufw

If you’re setting up a server with an external IP, you might want to disable ping and traceroute responses over the ICMP protocol.

To do this, open the /etc/ufw/before.rules file for editing:

BASH
sudo nvim /etc/ufw/before.rules
Click to expand and view more

Find and comment out the rule blocks related to ICMP:

BASH
# ok icmp codes for INPUT
# -A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
# -A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
# -A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
# -A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT

# ok icmp code for FORWARD
# -A ufw-before-forward -p icmp --icmp-type destination-unreachable -j ACCEPT
# -A ufw-before-forward -p icmp --icmp-type time-exceeded -j ACCEPT
# -A ufw-before-forward -p icmp --icmp-type parameter-problem -j ACCEPT
# -A ufw-before-forward -p icmp --icmp-type echo-request -j ACCEPT
Click to expand and view more

After that restart the firewall:

BASH
sudo ufw reload
Click to expand and view more

Try running ping and traceroute -I to your server, there should be no responses.

Blocking ICMP using kernel parameters

You can block ICMP responses in Linux at the kernel level using the sysctl utility or by editing files in /proc/sys.

💡 sysctl is a convenient wrapper for reading and writing Linux kernel parameters in the /proc/sys virtual filesystem.

For a temporary block, run these commands:

BASH
sysctl -w net.ipv4.icmp_echo_ignore_all=1

# or

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
Click to expand and view more

💡For ipv6 replace 4 with 6.

To allow ICMP responses again:

BASH
sysctl -w net.ipv4.icmp_echo_ignore_all=0

# or

echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
Click to expand and view more

If you need to make the changes permanent — add these parameters to the /etc/sysctl.conf file:

BASH
sudo nvim /etc/sysctl.conf
Click to expand and view more
BASH
net.ipv4.icmp_echo_ignore_all = 1
net.ipv6.icmp.echo_ignore_all = 1
Click to expand and view more

After that, reread the config:

BASH
sysctl -p
Click to expand and view more

It’s worth noting that this doesn’t fully disable ICMP interaction. For example, it’s worth studying the following parameters:

See the description of these and other ICMP parameters here 🧐.

Changing the server hostname and editing the hosts file

To change our system’s hostname, open the corresponding file for editing:

BASH
sudo nvim /etc/hostname
Click to expand and view more

I’ll set it to:

BASH
r4ven-test
Click to expand and view more

Let’s also add our new hostname to the hosts file (in Linux this file serves the same function as in Windows):

BASH
sudo nvim /etc/hosts
Click to expand and view more

Add:

BASH
127.0.0.1    r4ven-test
Click to expand and view more

Creating a swap file (optional)

If you don’t have a swap file, it’s recommended to create one. A swap file can be either a separate partition or a simple file. More virtuality for everyone!

Let’s check for an active swap in the system:

BASH
free -m
Click to expand and view more

Creation and startup:

BASH
# create a 1GB file
sudo fallocate -l 1G /swap

# set read-write permissions for the owner only (root)
sudo chmod 600 /swap

# create swap from the file
sudo mkswap /swap

# activate it
sudo swapon /swap
Click to expand and view more

Let’s check again:

BASH
free -m
Click to expand and view more

We created a swap file and started it manually. But when the server restarts it won’t start on its own. To do that we need to add it to a special file that lists the filesystems that are mounted at system startup — /etc/fstab:

BASH
echo "/swap    none    swap    sw    0    0" | sudo tee -a /etc/fstab
Click to expand and view more

The -a option for the tee command is mandatory! It tells tee to append to the end of the file, rather than overwrite it.

Let’s check fstab just in case:

BASH
cat /etc/fstab
Click to expand and view more

Please be careful. Only add the swap file to autostart if its creation and activation succeeded. If not, there’s a risk that on the next system restart, the system simply won’t boot.

Installing and configuring locales and timezone

Every system has such an entity as a “locale” — in other words, localization. By default, if you don’t choose otherwise during Debian 12 installation, the C.UTF-8 UTF-8 locale is installed. Let’s install the recommended English one: en_US.UTF-8 UTF-8 and add the Russian one: ru_RU.UTF-8 UTF-8 locale. This is done like this:

BASH
# installing the locales package
sudo apt install -y locales

# interactive locale configuration
sudo dpkg-reconfigure locales
Click to expand and view more

Using the Space key, check the box for en_US.UTF-8 UTF-8 (mandatory) and a second one for the one you need (in my case that’s ru_RU.UTF-8 UTF-8). Then use the Tab key to move the cursor to the <Ok> button and press Enter.

Next choose the main locale, which is exactly what will be used to display system information. English text doesn’t scare me (especially with a translator), so I’ll leave en_US.UTF-8:

By the way, I have a short note on a great translator: Crow Translate — The Most Convenient Text Translator.

Use the Tab key to move the cursor to <Ok> and press Enter. A couple of seconds and the locales are generated:

Now let’s set the time zone for our server with this command:

BASH
sudo timedatectl set-timezone Europe/Moscow
Click to expand and view more

You can see the list of all available time zones with the command:

BASH
timedatectl list-timezones
Click to expand and view more

System log rotation

By default in Debian GNU/Linux, system event logging is configured without limits on the number of stored files. To avoid finding a disk completely filled with system logs in the future, it’s recommended to set limits.

In Debian 12 the systemd program is responsible for system initialization, including logging. Log management is performed by a specific utility from the systemd package — journald.

You can limit the number of logs with it:

To do this, edit the logging service config:

BASH
sudo nvim /etc/systemd/journald.conf
Click to expand and view more

Where SystemMaxUse=800M is the size limit for stored logs, and MaxFileSec=2week is the time limit.

Choose the number of days and size based on your needs, or preferences.

Don’t forget to save and restart the service:

BASH
sudo systemctl restart systemd-journald
Click to expand and view more

If you find yourself in a situation where logs have already accumulated, you can clear them with special commands.

By time:

BASH
sudo journalctl --vacuum-time=15d
Click to expand and view more

By size:

BASH
sudo journalctl --vacuum-size=800M
Click to expand and view more

Configuring the SSH server

Open the SSH server configuration file:

BASH
sudo nvim /etc/ssh/sshd_config
Click to expand and view more

And set the following parameters:

PLAINTEXT
# change the default connection port
Port 27244

# enable using IPv4 only
AddressFamily inet

# disable the ability to log in as the root user
PermitRootLogin no

# explicitly allow key-based connections
PubkeyAuthentication yes

# disable password-based connections
PasswordAuthentication no

# disable authorization via the PAM subsystem
UsePAM no

# optionally, allow connections only for specified users
AllowUsers ivan
Click to expand and view more

Please be careful with the SSH server settings. The configuration I’m demonstrating heavily restricts the ability to connect to the server. Allowing connections only via SSH keys and only for the specified users.

If something is configured incorrectly, you may lose access to your server. Remember that this article is for recommendation purposes only. You perform all actions at your own risk.

Overall, the config file is fairly well documented with comments from the developers. Configuring it shouldn’t cause difficulties.

Let’s check the correctness of the config file with a special command:

BASH
sudo sshd -t
Click to expand and view more

Here’s an example of what happens if the config fails validation:

The system tells us there’s an invalid parameter on line 15. Let’s fix it, then again:

BASH
sudo sshd -t
Click to expand and view more

And if the output is empty, that means the check passed. Let’s restart the SSH server:

BASH
sudo systemctl restart sshd
Click to expand and view more

If you’re lucky and weren’t dropped, then, without disconnecting from the current SSH session, open a new terminal window or tab and try to connect to the server. Remember that we changed the SSH connection port from the standard 22 to 27244. To tell the ssh command to use a different port, use the -p option:

BASH
ssh ivan@192.168.56.102 -p 27244
Click to expand and view more

If the authorization succeeds, congratulations. If not we’ll talk about that later, go back to the neighboring session and check what you did wrong. Don’t close this backup session until you’ve achieved other successful logins.

Closing port 22 in UFW

Now we can safely close port 22 in ufw. To delete a rule, you need to insert the word delete before the command that added the rule:

BASH
sudo ufw delete allow 22/tcp

sudo ufw reload

sudo ufw status verbose
Click to expand and view more

Setting up brute-force protection — fail2ban

Fail2Ban is a network security tool for Linux that automatically blocks IP addresses engaging in suspicious activity (for example, multiple failed SSH login attempts). It analyzes logs (text files or journald) and applies temporary or permanent bans through iptables or nftables.

This step is especially useful if you haven’t disabled password-based SSH connections to the server (I don’t recommend doing that).

❗Please make sure you have physical access to the server or to its console from the hypervisor before making the following changes.

Below I’ll show the basic setup of fail2ban to protect the server from brute-force attacks, using SSH as an example.

Let’s install the package from the standard repositories:

BASH
sudo apt install -y fail2ban
Click to expand and view more

💡 Main fail2ban configuration files/directories:

  • /etc/fail2ban/fail2ban.conf — main daemon config;
  • /etc/fail2ban/jail.conf — jail settings template (we do NOT TOUCH this);
  • /etc/fail2ban/jail.local — local jail configuration (we WILL edit THIS);
  • /etc/fail2ban/filter.d/ — filter templates (regular expressions for logs);
  • /etc/fail2ban/action.d/ — action templates (what to do on a violation);

The package already comes with a set of ready-made filters for popular services, such as sshd, nginx, apache, etc. (take a look in /etc/fail2ban/filter.d/) and actions: iptables, nftables, firewall-cmd, etc. Also, nothing stops you from writing your own filters, if you’re good with regex 😉.

Let’s open the user config file for editing:

BASH
sudo nvim /etc/fail2ban/jail.local
Click to expand and view more

⚠️ Don’t confuse it with the main config /etc/fail2ban/jail.conf, which is not recommended to edit.

And fill it in:

PLAINTEXT
[DEFAULT]
# exceptions for local networks
ignoreip = 127.0.0.0/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
# IP ban duration (s|m|h)
bantime = 1h
# period over which failed attempts are counted
findtime = 10m
# number of failed attempts before blocking
maxretry = 5
# use nftables for banning
banaction = nftables-multiport
# default action
action = %(action_)s

[sshd]
# enable protection for ssh
enabled = true
# SSH server port, used for firewall rules
port = 27244
# which logs to read (this can be a path to a text file)
logpath = %(sshd_log)s
# backend for reading logs - journald
backend = systemd
Click to expand and view more

☝️The firewall’s default ban action is reject, which means sending a connection refused message to blocked addresses. To avoid sending this notification, change the action in the banaction parameter to nftables-multiport[blocktype=drop].

Let’s enable the service’s autostart, restart it, and check the status:

BASH
sudo systemctl enable fail2ban

sudo systemctl restart fail2ban

sudo systemctl status fail2ban
Click to expand and view more

To view the block status, use these commands:

BASH
sudo fail2ban-client status

sudo fail2ban-client status sshd
Click to expand and view more

Now, on another device (not the one you’re configuring the server from!), or if the server has a public IP then from a different network, try to make 5 failed SSH connection attempts. Example:

BASH
ssh test@192.168.56.102 -p 27244
Click to expand and view more

💡It’s worth noting that IPs from which failed attempts to connect with a private key were made also get banned. But by default, addresses that use logins of existing users are not banned in such connections. To also ban such connections, add the mode = aggressive parameter to the [DEFAULT] section. But be careful not to block yourself☝️.

The command below lets you view the sshd ban status in real time:

BASH
sudo watch -n2 fail2ban-client status sshd
Click to expand and view more

☝️ The watch command in Linux runs the command passed as an argument at a given interval, in my example 2 sec.

The fail2ban package also comes with a utility for checking regular expressions and matching log entries against them. In the case of SSH you can view matches like this:

BASH
sudo fail2ban-regex systemd-journald /etc/fail2ban/filter.d/sshd.conf
Click to expand and view more

In Debian 12, nftables is used as the firewall. Let’s look at the rules created by the fail2ban daemon:

BASH
sudo nft list table inet f2b-table
Click to expand and view more

To unban a specific address use the command:

BASH
sudo fail2ban-client set sshd unbanip 1.2.3.4
Click to expand and view more

See the list of available client utility commands with descriptions on the man page.

Final reboot

After completing all checks, let’s do a preventive restart of the Debian 12 server to make sure we’ve configured everything correctly:

BASH
sudo reboot
Click to expand and view more

After a while let’s try to connect to the server again:

BASH
ssh ivan@192.168.56.102 -p 27244
Click to expand and view more

Hooray, a small victory) As we can see, the hostname has also been updated.

Now you could, for example, set up Zshell following my article, or something else.

Installing and configuring ZSH (optional)

If you’re too lazy to read the whole article, here’s the TLDR:

BASH
# installing Z-Shell and helper utilities
sudo apt update && sudo apt install -y zsh fzf git curl

# checking that everything installed correctly
whereis {zsh,fzf,git,curl}

# installing Oh-My-Zsh - answer yes
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# installing the zsh-autosuggestions plugin
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

# installing the fast-syntax-highlighting plugin
git clone https://github.com/zdharma-continuum/fast-syntax-highlighting ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/fast-syntax-highlighting

# installing the cmdtime plugin
git clone https://github.com/tom-auger/cmdtime ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/cmdtime

# changing the theme to agnoster
sed -i 's/^ZSH_THEME=.*/ZSH_THEME="agnoster"/' ~/.zshrc

# disabling Oh-My-Zsh auto-updates
sed -i "s/^#.*omz:update.*disabled/zstyle ':omz:update' mode disabled/" ~/.zshrc

# enabling the installed plugins
sed -i 's/^plugins=\(.*\)/plugins=\(git zsh-autosuggestions fast-syntax-highlighting fzf cmdtime\)/' ~/.zshrc

# setting colors for the fzf plugin (this is all one command)
echo "export FZF_DEFAULT_OPTS=$FZF_DEFAULT_OPTS'
        --color=fg:#e5e9f0,bg:#3b4252,hl:#81a1c1
        --color=fg+:#e5e9f0,bg+:#3b4252,hl+:#81a1c1
        --color=info:#eacb8a,prompt:#bf6069,pointer:#b48dac
        --color=marker:#a3be8b,spinner:#b48dac,header:#a3be8b'\n\n\
        $(cat ~/.zshrc)" > ~/.zshrc

# applying the changes made
source ~/.zshrc

# for a minimalist prompt
echo "prompt_context() [ ]" >> ~/.zshrc && source ~/.zshrc

## setting up Oh-My-Zsh for the root user

# copying our user's files into the root user's directory
sudo cp -r {~/.oh-my-zsh,~/.zshrc} /root/

# changing root's default shell
sudo chsh -s /usr/bin/zsh

# switching to root to check
sudo -s
Click to expand and view more

Afterword

Well, there we go, we’ve performed the initial basic setup of a Debian 12 Linux server. Going forward, based on such a configuration, I’ll be installing and configuring various server software and, of course, writing articles about it.

So subscribe to our telegram to not miss new materials. Thanks for reading, penguins (:

Copyright Notice

Author: Ivan Cherniy

Link: https://r4ven.me/en/linux/nachalnaya-nastrojka-linux-servera-na-primere-debian/

License: CC BY-NC-SA 4.0

Blog materials may be used with attribution to the author and source, for non-commercial purposes, and under the same license.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut