Overview#
This article documents my Standard Operating Procedure (SOP) for initializing Linux servers. Not a universal guide, but a personal quick cheatsheet.
Related Reading:
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.
- bin456789/reinstall supports a wider variety of operating systems.
- 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:
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
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
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!