Overview

This article documents my Standard Operating Procedure (SOP) for initializing Linux servers. Not a universal guide, but a personal quick cheatsheet.

Related Reading:

Performance Benchmarks

Let’s inspect the cards (French accent~)

Before reinstalling, run a hardware baseline benchmark utilizing YABS (Yet Another Bench Script) as root:

1
curl -sL yabs.sh | bash -s -- -5

Below is an example of the benchmark output:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
root@ncrs1000:~# curl -sL yabs.sh | bash -s -- -5

# ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #

#              Yet-Another-Bench-Script              #

#                     v2025-04-20                    #

# https://github.com/masonr/yet-another-bench-script #

# ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #

Sat Feb 28 04:59:28 AM CET 2026

Basic System Information:

---------------------------------

Uptime     : 0 days, 0 hours, 1 minutes

Processor  : AMD EPYC 9634 84-Core Processor

CPU cores  : 4 @ 2246.622 MHz

AES-NI     : ✔ Enabled

VM-x/AMD-V : ❌ Disabled

RAM        : 7.8 GiB

Swap       : 0.0 KiB

Disk       : 503.9 GiB

Distro     : Debian GNU/Linux 13 (trixie)

Kernel     : 6.12.73+deb13-amd64

VM Type    : KVM

IPv4/IPv6  : ✔ Online / ✔ Online

IPv4 Network Information:

---------------------------------

ISP        : netcup GmbH

ASN        : AS197540 netcup GmbH

Host       : netcup GmbH

Location   : Karlsruhe, Baden-Wurttemberg (BW)

Country    : Germany

fio Disk Speed Tests (Mixed R/W 50/50) (Partition /dev/vda3):

---------------------------------

Block Size | 4k            (IOPS) | 64k           (IOPS)

  ------   | ---            ----  | ----           ----

Read       | 174.89 MB/s  (43.7k) | 716.07 MB/s  (11.1k)

Write      | 175.35 MB/s  (43.8k) | 719.84 MB/s  (11.2k)

Total      | 350.25 MB/s  (87.5k) | 1.43 GB/s    (22.4k)

           |                      |

Block Size | 512k          (IOPS) | 1m            (IOPS)

  ------   | ---            ----  | ----           ----

Read       | 1.29 GB/s     (2.5k) | 1.89 GB/s     (1.8k)

Write      | 1.36 GB/s     (2.6k) | 2.01 GB/s     (1.9k)

Total      | 2.65 GB/s     (5.1k) | 3.91 GB/s     (3.8k)

iperf3 Network Speed Tests (IPv4):

---------------------------------

Provider        | Location (Link)           | Send Speed      | Recv Speed      | Ping

-----           | -----                     | ----            | ----            | ----

Clouvider       | London, UK (10G)          | 1.85 Gbits/sec  | 1.19 Gbits/sec  | 15.0 ms

Eranium         | Amsterdam, NL (100G)      | 2.69 Gbits/sec  | 2.36 Gbits/sec  | 10.1 ms

Uztelecom       | Tashkent, UZ (10G)        | 2.49 Gbits/sec  | 1.14 Gbits/sec  | 82.2 ms

Leaseweb        | Singapore, SG (10G)       | 577 Mbits/sec   | 1.79 Gbits/sec  | --

Clouvider       | Los Angeles, CA, US (10G) | 1.10 Gbits/sec  | 442 Mbits/sec   | 158 ms

Leaseweb        | NYC, NY, US (10G)         | 2.21 Gbits/sec  | 2.14 Gbits/sec  | 89.9 ms

Edgoo           | Sao Paulo, BR (1G)        | 2.13 Gbits/sec  | 785 Mbits/sec   | 217 ms

iperf3 Network Speed Tests (IPv6):

---------------------------------

Provider        | Location (Link)           | Send Speed      | Recv Speed      | Ping

-----           | -----                     | ----            | ----            | ----

Clouvider       | London, UK (10G)          | 2.71 Gbits/sec  | busy            | 14.8 ms

Eranium         | Amsterdam, NL (100G)      | 2.69 Gbits/sec  | 2.31 Gbits/sec  | 10.1 ms

Uztelecom       | Tashkent, UZ (10G)        | 2.38 Gbits/sec  | 1.91 Gbits/sec  | 82.2 ms

Leaseweb        | Singapore, SG (10G)       | 2.15 Gbits/sec  | 1.86 Gbits/sec  | --

Clouvider       | Los Angeles, CA, US (10G) | 1.10 Gbits/sec  | 593 Mbits/sec   | 157 ms

Leaseweb        | NYC, NY, US (10G)         | 2.21 Gbits/sec  | 2.11 Gbits/sec  | 89.9 ms

Edgoo           | Sao Paulo, BR (1G)        | 1.95 Gbits/sec  | 591 Mbits/sec   | 217 ms

Geekbench 5 Benchmark Test:

---------------------------------

Test            | Value

                |

Single Core     | 1412

Multi Core      | 4832

Full Test       | https://browser.geekbench.com/v5/cpu/24146433

YABS completed in 8 min 44 sec

Post-test, verify CPU, RAM, storage, and bandwidth specs against provider claims.

Secondly, Geekbench 5 single-core scores dictate absolute performance ratings; benchmarks categorized below:

  • ~400 Single-Core: Entry-level; suitable for lightweight proxies, static pages, or personal blogs. Avoid high-concurrency workloads.
  • ~800 Single-Core: Average baseline; handles daily webhosting (WordPress), basic Docker containers, or small databases cleanly.
  • ~1200 Single-Core: Excellent performance; smoothly anchors heavier web apps, RDBMS databases (MySQL/PostgreSQL), or CI/CD pipelines.
  • >2000 Single-Core: Flagship tier; anchor single-thread rigorous games servers, heavy video transcoding, or high-concurrency backends easily.

For KVM designs, Multi-core scores should roughly equal Cores × Single-Core scores. Severe proportions divergence denotes heavy overselling setups (noisy neighbors).

If everything looks solid, let’s commence server setups layouts.

Reinstalling Operating System

Reinstallation carries risks; verify script integrity independently. Custom ISO triggers always suggested if supported.

Using One-Click DD Scripts

For providers neglecting custom ISOs, DD scripts work fine. Inspect author TOS first before blindly pasting commands.

  1. bin456789/reinstall supports a wider variety of operating systems.
  2. bohanwood/debi focuses on clean, minimal Debian system environments.

Taking Oracle ARM machines installing Debian as an example, the workflow using the debi script is as follows:

Download Script

1
curl -fLO https://raw.githubusercontent.com/bohanyang/debi/master/debi.sh && chmod +x debi.sh

Reinstallation Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sudo ./debi.sh \

  --version 13 \

  --architecture arm64 \

  --cloudflare \

  --user viamoe \

  --authorized-keys-url https://github.com/githubUserName.keys \

  --ssh-port 22122

Parameter breakdown:

  • --version 13 Specifies Debian 13 (codename Trixie)
  • --architecture arm64 Specifies system architecture; arm64 matches ARM-based machines here
  • --cloudflare Sets Cloudflare as the default DNS provider
  • --user viamoe Creates a standard user viamoe with sudo privileges
  • --authorized-keys-url https://github.com/githubUserName.keys Imports SSH public keys for passwordless logins
  • --ssh-port 22122 Changes the default SSH port to evade low-level brute-force scans

Begin Reinstallation

Rebooting initiates automatic network reinstall loops:

1
sudo reboot

Inspect progress via VNC setups; ready in minutes usually.

Using Custom ISO Images

If your provider supports uploading custom ISO images for manual installations, this is highly recommended for maintaining a clean base environment. For Netcup servers, you can also mount a custom ISO directly from the control panel and complete the installation manually.

Once the reinstallation completes, log in using the preset credentials and commence subsequent configurations.

Configuring Users

Granting standard user sudo privileges for simpler administration nodes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Switch to root user
su

# Install sudo
apt update && apt install -y sudo

# Passwordless sudo
# Replace dejavu with actual username
echo "dejavu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/dejavu

# Set permissions
chmod 440 /etc/sudoers.d/dejavu

# Exit root user

exit

Core Package Installation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
sudo apt update && apt upgrade

sudo apt install \

    apt-transport-https \

    build-essential \

    git \

    curl \

    wget \

    unzip \

    tmux \

    btop \

    bind9-dnsutils \

    tree \

    vim

SSH Security Hardening

Key-Based Login Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Create SSH directory
mkdir -p ~/.ssh

# Paste SSH public key
vim ~/.ssh/authorized_keys

# Or upload SSH public key
# ssh -i /path/to/your/ed25519_key username@<IP> -p <port>

chmod 700 ~/.ssh

chmod 600 ~/.ssh/authorized_keys

SSH Config Optimization

1
sudo vim /etc/ssh/sshd_config

Primary config edits below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Change default SSH port
Port 22122

# Login Grace Time
# Disconnect automatically after 1 min with no input
LoginGraceTime 1m

# Disable root login
PermitRootLogin no

# Strict mode; inspects home directory permissions
StrictModes yes

# Enable SSH public key authentication
PubkeyAuthentication yes

# Disable traditional password login
PasswordAuthentication no

# Disallow empty passwords
PermitEmptyPasswords no

# Disable keyboard-interactive auth (prevents bypassing password restricts via PAM)
KbdInteractiveAuthentication no

# Debian usually requires 'yes', otherwise certain session features might break.
UsePAM yes

# Disable X11 Forwarding
X11Forwarding no

# Maintain SSH session alive
# Server sends keepalive heartbeats every 60 seconds
ClientAliveInterval 60

# Disconnect if client stays unresponsive for 5 cycles
ClientAliveCountMax 5

# Disable reverse DNS lookups
UseDNS no

Hold current session, establish second SSH bind separately to verify locks before disconnecting.

1
2
3
4
5
# Inspect SSH config
sudo sshd -t

# Apply new SSH config
sudo systemctl restart sshd

IPv6 Static Routing

Netcup servers provide a /64 IPv6 range; configuring static routing is suggested (exact layouts vary across vendors):

1
sudo vim /etc/network/interfaces

Append:

1
2
3
4
5
6
7
8
9
iface ens3 inet6 static
    address <Static_IPv6_Address>
    netmask 64

    # Netcup network gateway
    gateway fe80::1

    # Accept router advertisements
    accept_ra 2

Then execute:

1
sudo ip addr flush dev ens3

Replacing Linux Kernel

Cloud kernels highly optimized for virtualization are recommended.

1
sudo apt install -y linux-image-cloud-amd64

Reboot to load the new kernel:

1
2
3
4
5
6
7
sudo reboot

uname -r

# Output

6.12.63+deb13-cloud-amd64

Cleaning residual kernels:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# List installed kernel packages

sudo dpkg --list | grep linux-image

# Output example

ii  linux-image-6.12.63+deb13-amd64       6.12.63-1                            amd64        Linux 6.12 for 64-bit PCs (signed)

ii  linux-image-6.12.63+deb13-cloud-amd64 6.12.63-1                            amd64        Linux 6.12 for x86-64 cloud (signed)

ii  linux-image-amd64                     6.12.63-1                            amd64        Linux for 64-bit PCs (meta-package)

ii  linux-image-cloud-amd64               6.12.63-1                            amd64        Linux for x86-64 cloud (meta-package)

# Remove generic kernel metapackages and specific versions (replace based on output)

sudo apt purge -y linux-image-amd64 linux-image-6.12.63+deb13-amd64

# Update GRUB

sudo update-grub

# Run cleanup

sudo apt autoremove --purge -y

Installing Docker

Refer to the Docker official docs for sequential steps; commands below carry no timeliness guarantees:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Add Docker official GPG key

sudo apt-get update

sudo apt-get install ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add software source

echo \

  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \

  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \

  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo systemctl status docker

Supplementary notes: Enabling IPv6 Support in Docker

Installing Nginx

Similarly, refer to Nginx project documentation for install steps on Debian:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
sudo apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring

# Import GPG key

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \

    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

    

# Verify GPG key

mkdir -m 700 ~/.gnupg

gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \

http://nginx.org/packages/debian \`lsb_release -cs\` nginx" \

    | sudo tee /etc/apt/sources.list.d/nginx.list

# Set repository locking; prioritize official Nginx packages

echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \

    | sudo tee /etc/apt/preferences.d/99nginx

# Install Nginx

sudo apt update && sudo apt install nginx

# Start Nginx

sudo systemctl start nginx

Simulating Fallback vhost sets with self-signed SSL/TLS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Prepare directory

sudo mkdir -p /etc/nginx/cert

sudo chmod 700 /etc/nginx/cert

# Generate certificate

sudo openssl req -x509 -new -nodes -newkey rsa:2048 \

  -sha256 \

  -days 3650 \

  -keyout /etc/nginx/cert/deny.key \

  -out /etc/nginx/cert/deny.pem \

  -subj "/C=XX/ST=Denied/L=Denied/O=Denied/CN=invalid.local" \

  -addext "subjectAltName=DNS:invalid.local"

  

# Set permissions

sudo chmod 600 /etc/nginx/cert/deny.key

sudo chmod 644 /etc/nginx/cert/deny.pem

Create specific virtual host file:

1
sudo vim /etc/nginx/conf.d/00-default.conf

Content below Layouts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server {

    listen 80 default_server;

    listen [::]:80 default_server;

    server_name _;

    return 444; 

}

server {

    listen 443 ssl default_server;

    listen [::]:443 ssl default_server;

    server_name _;

    

    ssl_certificate /etc/nginx/cert/deny.pem; 

    ssl_certificate_key /etc/nginx/cert/deny.key;

    ssl_protocols TLSv1.2 TLSv1.3;

    ssl_prefer_server_ciphers off;

    error_page 497 =444 /dev/null;

    return 444;

}

Reload Nginx configuration

1
2
3
sudo nginx -t

sudo nginx -s reload

ZRAM and Swap Configurations

Balance disk I/O performance and CPU metrics correctly.

Configuring ZRAM

ZRAM compresses data inside memory blocks, exceeding disk Swap benchmarks drastically.

1
2
3
# Install zram-tools management utilities

sudo apt update && sudo apt install -y zram-tools

Edit ZRAM configuration

1
sudo vim /etc/default/zramswap

Parameter breakdown:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Select zstd algorithm for optimized ratio/speed balance

ALGO=zstd

# Allocate ZRAM proportions

# 1GB RAM: 100% suggested

# 2~4GB RAM: 60% suggested

# 8GB RAM+: 25% suggested

PERCENT=70

# Priority 100 ensures system utilizes ZRAM first

PRIORITY=100

Apply ZRAM configuration

1
sudo systemctl restart zramswap

Configuring Swap

Swapfile provides better flexibility than Swap partitions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Create 2GB Swapfile

# 1024 * 2 = 2048

sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress

# Set permissions

sudo chmod 600 /swapfile

# Format Swap

sudo mkswap /swapfile

# Swap operates with lower priority than ZRAM

sudo swapon --priority -2 /swapfile

# Automount on boot

echo '/swapfile none swap sw,pri=-2 0 0' | sudo tee -a /etc/fstab

# Verify mount points

sudo mount -a

Kernel Parameters Adjustment

Adjust Swap aggressiveness based on physical RAM.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1GB RAM (Aggressive)

echo "vm.swappiness=100" | sudo tee -a /etc/sysctl.conf

# 2GB/4GB RAM (Active)

echo "vm.swappiness=60" | sudo tee -a /etc/sysctl.conf

# 8GB RAM+ (Conservative)

echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf

For machines under 2GB RAM, releasing file caches is usually preferred:

1
echo "vm.vfs_cache_pressure=50" | sudo tee -a /etc/sysctl.conf

Apply configuration

1
sudo sysctl -p

Verify State

1
2
3
sudo swapon --show

sudo zramctl

SSD Trim Optimization

For NVMe layouts, enabling Trim auto-optimization is advisable.

1
2
3
# Scheduled task

sudo systemctl enable --now fstrim.timer

Not all providers support Trim on their VPS/VDS layouts; verify independently:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# DISC-MAX column shouldn't read 0

lsblk -D

NAME   DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO

sr0           0        0B       0B         0

zram0         0        4K       2T         0

vda           0      512B       2G         0

├─vda1        0      512B       2G         0

└─vda2        0      512B       2G         0

# Try manual trigger

sudo fstrim -v /

/: 434.4 GiB (466435428352 bytes) trimmed

NTP Timezone Synchronization

Set UTC Timezone

Setting timezone to UTC offsets cross-region log-divergence setups impeccably.

1
sudo timedatectl set-timezone UTC

Sync Time via Chrony

Chrony offers superior precision layouts over systemd-timesyncd.

1
2
3
4
5
# Install Chrony client

sudo apt update

sudo apt install chrony -y

Add NTP servers:

1
sudo vim /etc/chrony/chrony.conf

Comment out default pool and server lines, then append Cloudflare NTP nodes:

1
2
3
4
5
6
7
server time.cloudflare.com iburst

server time.cloudflare.com iburst

server time.cloudflare.com iburst

server time.cloudflare.com iburst

Start Chrony service

1
2
3
sudo systemctl mask systemd-timesyncd.service

sudo systemctl enable --now chrony

Verify synchronization status

1
chronyc sources -v

Configuring nftables Firewall

nftables modernizes descriptive filtering grids replacing obsolete iptables/UFW layers inside Debian frameworks.

Check Environment

Starting with Debian 10, nftables is installed by default but usually inactive. Verify:

1
2
3
4
5
6
7
# Ensure nftables is installed

sudo apt update && sudo apt install nftables -y

# Default 'inactive' status is normal

sudo systemctl status nftables

If UFW was previously installed, uninstalling is recommended to avoid conflicts:

1
sudo ufw disable && sudo apt purge ufw -y

Understand Configuration

Before writing rules, simply master three tiers:

  • Table: Top-level container for rules
  • Chain: Rule sets bound to network hooks (INPUT/FORWARD/OUTPUT), defining default policies (Drop or Accept)
  • Rule: Specific matching conditions and actions

Getting Started

The following layout represents a typical “secure” server profile: only allows Cloudflare CDN back-to-origin access on ports 80/443, locking down everything except the SSH port.

1
2
3
# Edit config file

sudo vim /etc/nftables.conf

Syntax is simple; refer to comments:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/sbin/nft -f

# Flush ruleset

flush ruleset

table inet filter {

    # ============================================================

    # IP Sets

    # ============================================================

    set cloudflare_v4 {

        type ipv4_addr; flags interval;

        elements = {

            173.245.48.0/20,

            103.21.244.0/22,

            103.22.200.0/22,

            103.31.4.0/22,

            141.101.64.0/18,

            108.162.192.0/18,

            190.93.240.0/20,

            188.114.96.0/20,

            197.234.240.0/22,

            198.41.128.0/17,

            162.158.0.0/15,

            104.16.0.0/13,

            104.24.0.0/14,

            172.64.0.0/13,

            131.0.72.0/22

        }

    }

    set cloudflare_v6 {

        type ipv6_addr; flags interval;

        elements = {

            2400:cb00::/32,

            2606:4700::/32,

            2803:f800::/32,

            2405:b500::/32,

            2405:8100::/32,

            2a06:98c0::/29,

            2c0f:f248::/32

        }

    }

    # ============================================================

    # INPUT Chain (Ingress)

    # ============================================================

    chain input {

       # Drop non-matching traffic

        type filter hook input priority filter; policy drop;

        # Allow established/related sessions

        ct state established, related accept

        # Drop invalid packets

        ct state invalid drop

        # Allow loopback interface

        iif "lo" accept

        # Allow IPv6 NDP

        ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert } accept

        # Allow ICMP (Rate-limited)

        ip protocol icmp limit rate 4/second accept

        ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate 4/second accept

        # Allow SSH port

        tcp dport 22122 accept

        # Restrict 80/443 access to Cloudflare IPs only

        ip saddr @cloudflare_v4 tcp dport { 80, 443 } accept

        ip6 saddr @cloudflare_v6 tcp dport { 80, 443 } accept

    }

    # ============================================================

    # FORWARD Chain (Routing)

    # ============================================================

    chain forward {

        # Drop non-matching traffic

        type filter hook forward priority filter; policy drop;

        # Allow established/related sessions

        ct state established, related accept

        # Drop invalid packets

        ct state invalid drop

        # Allow Docker egress (bridges)

        iifname "docker0" accept

        iifname "br-*" accept

        # Allow Docker container-to-container comms

        iifname "docker0" oifname "docker0" accept

        iifname "br-*" oifname "br-*" accept

    }

    # ============================================================

    # OUTPUT Chain (Egress)

    # ============================================================

    chain output {

        # Default policy: accept all egress

        type filter hook output priority filter; policy accept;

    }

}

Apply Rules

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Check syntax

sudo nft -c -f /etc/nftables.conf

# Start service

sudo systemctl enable --now nftables

# Rules auto-inject upon Docker restarts

sudo systemctl restart nftables && sudo systemctl restart docker

# Test Docker egress

sudo docker run --rm busybox ping -c 4 dejavu.moe

Fail2ban Installation & Setup

1
2
3
4
5
6
7
# Install Fail2ban

sudo apt update && sudo apt install fail2ban -y

# Create a minimal SSH protection layout

sudo vim /etc/fail2ban/jail.local

Config setup below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[DEFAULT]

# Ignore localhost to prevent self-bans

ignoreip = 127.0.0.1/8 ::1

# Ban for 1 day

bantime  = 1d

# Trigger after accumulated failures in 10 mins

findtime = 10m

# Max retries before ban

maxretry = 3

# Automatic nftables integration

banaction = nftables-multiport

banaction_allports = nftables-allports

[sshd]

# Enable SSH protection

enabled = true

# SSH listening port (must strictly match!)

port    = 22122

# Use systemd log backend

backend = systemd

# Strictly match failure logs

mode    = aggressive

Start Fail2ban service

1
2
3
sudo systemctl enable --now fail2ban

sudo systemctl restart nftables && sudo systemctl restart fail2ban

Test banning rules:

1
2
3
4
5
6
7
# Ban a test IP

sudo fail2ban-client set sshd banip 2400:6180:0:d2:0:2:9699:d000

# Check for f2b tables in nftables

sudo nft list ruleset | grep f2b

Check Fail2ban configuration

1
2
3
4
5
6
7
# Inspect 'jails'

sudo fail2ban-client status

# Check banned IPs

sudo fail2ban-client status sshd

Your secure server baseline is now complete. Happy Hosting!