[{"content":"Any server with SSH exposed to the internet will see constant brute-force login attempts. fail2ban is a simple, effective way to deal with them.\nInstallation apt update \u0026amp;\u0026amp; apt install -y fail2ban Configuration Never edit /etc/fail2ban/jail.conf directly — it gets overwritten on updates. Create a local override:\ncat \u0026gt; /etc/fail2ban/jail.local \u0026lt;\u0026lt; EOF [sshd] enabled = true port = 22 logpath = /var/log/auth.log maxretry = 3 bantime = 7200 findtime = 600 EOF This configuration:\nMonitors SSH login attempts in auth.log Bans an IP after 3 failed attempts within 10 minutes Ban lasts 2 hours Start the service:\nsystemctl enable fail2ban systemctl start fail2ban Monitoring Check current jail status:\nfail2ban-client status sshd Sample output:\nStatus for the jail: sshd |- Filter | |- Currently failed: 2 | |- Total failed: 847 | `- File list: /var/log/auth.log `- Actions |- Currently banned: 3 |- Total banned: 156 `- Banned IP list: 203.0.113.42 198.51.100.7 192.0.2.15 Common Operations # Unban a specific IP (when you accidentally lock yourself out) fail2ban-client set sshd unbanip 203.0.113.42 # View fail2ban log tail -50 /var/log/fail2ban.log # Temporarily disable (emergency access) systemctl stop fail2ban Tuning Tips Don\u0026rsquo;t set maxretry too low: maxretry = 1 will likely ban you at some point when you mistype a password Reasonable bantime: 2 hours is usually enough to deter script kiddies without causing long lockouts Consider ignoreip: Add your static IP to prevent self-bans: ignoreip = 127.0.0.1/8 ::1 your.static.ip Check before banning yourself: If you\u0026rsquo;re changing SSH configs remotely, keep a second session open as a safety net When fail2ban Isn\u0026rsquo;t Enough For serious hardening, also consider:\nDisabling password auth entirely (PasswordAuthentication no) and using key-based auth only Moving SSH to a non-standard port (security through obscurity, but reduces log noise) Using AllowUsers to restrict which accounts can SSH in fail2ban is a good baseline defense, but it shouldn\u0026rsquo;t be your only layer.\n","permalink":"https://blog.dusong.ai/posts/fail2ban-ssh/","summary":"\u003cp\u003eAny server with SSH exposed to the internet will see constant brute-force login attempts. fail2ban is a simple, effective way to deal with them.\u003c/p\u003e\n\u003ch2 id=\"installation\"\u003eInstallation\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eapt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e apt install -y fail2ban\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"configuration\"\u003eConfiguration\u003c/h2\u003e\n\u003cp\u003eNever edit \u003ccode\u003e/etc/fail2ban/jail.conf\u003c/code\u003e directly — it gets overwritten on updates. Create a local override:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecat \u0026gt; /etc/fail2ban/jail.local \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;\u0026lt; EOF\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e[sshd]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eenabled = true\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eport = 22\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003elogpath = /var/log/auth.log\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003emaxretry = 3\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ebantime = 7200\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003efindtime = 600\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis configuration:\u003c/p\u003e","title":"Practical fail2ban Configuration for SSH Protection"},{"content":"Managing SSL certificates manually is tedious and error-prone. Here\u0026rsquo;s how I set up fully automated certificate management using acme.sh with Cloudflare DNS validation.\nWhy acme.sh + DNS-01? No port 80 required: DNS-01 validation doesn\u0026rsquo;t need a running web server or open HTTP port Wildcard support: Can issue *.example.com certificates Cloudflare integration: API-based, fully automated Lightweight: Pure shell script, no dependencies Installation curl https://get.acme.sh | sh -s email=you@example.com source ~/.bashrc Cloudflare API Token Create a token at Cloudflare Dashboard with these permissions:\nZone - DNS - Edit (for the specific zone) Zone - Zone - Read Export the credentials:\nexport CF_Token=\u0026#34;your-api-token\u0026#34; export CF_Zone_ID=\u0026#34;your-zone-id\u0026#34; Issue a Certificate acme.sh --issue --dns dns_cf -d example.com -d \u0026#39;*.example.com\u0026#39; --keylength ec-256 The ec-256 flag requests an ECDSA P-256 certificate, which is smaller and faster than RSA.\nInstall the Certificate Don\u0026rsquo;t use the files in ~/.acme.sh/ directly. Use the install command to copy them to the right location:\nacme.sh --install-cert -d example.com --ecc \\ --key-file /etc/ssl/private/example.com.key \\ --fullchain-file /etc/ssl/certs/example.com.pem \\ --reloadcmd \u0026#34;systemctl reload nginx\u0026#34; Auto-Renewal acme.sh sets up a cron job automatically. Verify:\ncrontab -l | grep acme You should see something like:\n0 0 * * * \u0026#34;/root/.acme.sh\u0026#34;/acme.sh --cron --home \u0026#34;/root/.acme.sh\u0026#34; \u0026gt; /dev/null Certificates are renewed 30 days before expiry.\nTroubleshooting DNS propagation delay: Add --dnssleep 120 if validation fails intermittently Rate limits: Use --staging for testing to avoid Let\u0026rsquo;s Encrypt rate limits Check certificate: echo | openssl s_client -connect example.com:443 2\u0026gt;/dev/null | openssl x509 -noout -dates This setup has been running on my servers for months without any manual intervention. One less thing to worry about.\n","permalink":"https://blog.dusong.ai/posts/acme-sh-letsencrypt/","summary":"\u003cp\u003eManaging SSL certificates manually is tedious and error-prone. Here\u0026rsquo;s how I set up fully automated certificate management using acme.sh with Cloudflare DNS validation.\u003c/p\u003e\n\u003ch2 id=\"why-acmesh--dns-01\"\u003eWhy acme.sh + DNS-01?\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eNo port 80 required\u003c/strong\u003e: DNS-01 validation doesn\u0026rsquo;t need a running web server or open HTTP port\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWildcard support\u003c/strong\u003e: Can issue \u003ccode\u003e*.example.com\u003c/code\u003e certificates\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCloudflare integration\u003c/strong\u003e: API-based, fully automated\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLightweight\u003c/strong\u003e: Pure shell script, no dependencies\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"installation\"\u003eInstallation\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl https://get.acme.sh | sh -s email\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eyou@example.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esource ~/.bashrc\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"cloudflare-api-token\"\u003eCloudflare API Token\u003c/h2\u003e\n\u003cp\u003eCreate a token at \u003ca href=\"https://dash.cloudflare.com/profile/api-tokens\"\u003eCloudflare Dashboard\u003c/a\u003e with these permissions:\u003c/p\u003e","title":"Automating Let's Encrypt Certificates with acme.sh"},{"content":"If you\u0026rsquo;re running a server that handles long-distance TCP connections, switching from the default Cubic to BBR can make a noticeable difference in throughput.\nWhat is BBR? BBR (Bottleneck Bandwidth and Round-trip propagation time) is a congestion control algorithm developed by Google. Unlike loss-based algorithms like Cubic, BBR tries to model the actual bottleneck bandwidth and RTT, leading to better performance on lossy or high-latency links.\nCheck Current Algorithm sysctl net.ipv4.tcp_congestion_control Most modern kernels default to cubic.\nEnable BBR Requires Linux kernel 4.9+ (Debian 12 ships with 6.1, so we\u0026rsquo;re good):\ncat \u0026gt;\u0026gt; /etc/sysctl.conf \u0026lt;\u0026lt; EOF net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr EOF sysctl -p Verify sysctl net.ipv4.tcp_congestion_control # Expected: net.ipv4.tcp_congestion_control = bbr lsmod | grep bbr # Should show tcp_bbr module Does It Actually Help? In my testing on a CN2 GIA link (US West Coast to China), BBR improved download throughput by roughly 30-40% during peak hours compared to Cubic. The improvement is most noticeable when the link has moderate packet loss (0.5-2%).\nFor low-latency, low-loss local connections, the difference is negligible. BBR really shines on intercontinental links where packet loss is unavoidable.\nCaveats BBR v1 (what most distros ship) can be aggressive and may not play nicely with other flows on shared links BBR v2/v3 are in development and address fairness concerns If you\u0026rsquo;re on a shared hosting platform, changing congestion control might not be allowed For a dedicated VPS, there\u0026rsquo;s really no reason not to enable it.\n","permalink":"https://blog.dusong.ai/posts/bbr-congestion-control/","summary":"\u003cp\u003eIf you\u0026rsquo;re running a server that handles long-distance TCP connections, switching from the default Cubic to BBR can make a noticeable difference in throughput.\u003c/p\u003e\n\u003ch2 id=\"what-is-bbr\"\u003eWhat is BBR?\u003c/h2\u003e\n\u003cp\u003eBBR (Bottleneck Bandwidth and Round-trip propagation time) is a congestion control algorithm developed by Google. Unlike loss-based algorithms like Cubic, BBR tries to model the actual bottleneck bandwidth and RTT, leading to better performance on lossy or high-latency links.\u003c/p\u003e\n\u003ch2 id=\"check-current-algorithm\"\u003eCheck Current Algorithm\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esysctl net.ipv4.tcp_congestion_control\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eMost modern kernels default to \u003ccode\u003ecubic\u003c/code\u003e.\u003c/p\u003e","title":"Enabling BBR Congestion Control on Linux"},{"content":"WireGuard has become my go-to VPN solution for connecting remote machines. It\u0026rsquo;s fast, simple, and the configuration is refreshingly minimal compared to OpenVPN or IPSec.\nWhy WireGuard? After years of wrestling with OpenVPN configs, WireGuard feels like a breath of fresh air:\nPerformance: Runs in kernel space, significantly less overhead Simplicity: A single config file per interface Modern cryptography: ChaCha20, Curve25519, BLAKE2s — no cipher negotiation headaches Small codebase: ~4,000 lines of code vs. OpenVPN\u0026rsquo;s ~100,000+ Installation On Debian 12 (Bookworm), WireGuard is available directly:\napt update \u0026amp;\u0026amp; apt install -y wireguard Verify the module is loaded:\nmodprobe wireguard lsmod | grep wireguard Key Generation Each peer needs a private/public key pair:\nwg genkey | tee /etc/wireguard/private.key | wg pubkey \u0026gt; /etc/wireguard/public.key chmod 600 /etc/wireguard/private.key Server Configuration Create /etc/wireguard/wg0.conf:\n[Interface] PrivateKey = \u0026lt;server-private-key\u0026gt; Address = 10.0.0.1/24 ListenPort = 51820 PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = \u0026lt;client-public-key\u0026gt; AllowedIPs = 10.0.0.2/32 Start it up:\nwg-quick up wg0 systemctl enable wg-quick@wg0 Verifying the Tunnel wg show wg0 You should see the peer listed with a recent handshake timestamp. If latest handshake shows \u0026ldquo;never\u0026rdquo;, check firewall rules and ensure UDP port 51820 is open.\nTips PersistentKeepalive: Set to 25 on the client side if behind NAT, this keeps the tunnel alive DNS: If routing all traffic through the tunnel, don\u0026rsquo;t forget to set DNS = 1.1.1.1 on the client MTU: Default is usually fine, but if you see packet fragmentation issues, try setting MTU = 1380 WireGuard is one of those tools that just works once configured correctly. The hardest part is usually getting the firewall rules right.\n","permalink":"https://blog.dusong.ai/posts/wireguard-debian12/","summary":"\u003cp\u003eWireGuard has become my go-to VPN solution for connecting remote machines. It\u0026rsquo;s fast, simple, and the configuration is refreshingly minimal compared to OpenVPN or IPSec.\u003c/p\u003e\n\u003ch2 id=\"why-wireguard\"\u003eWhy WireGuard?\u003c/h2\u003e\n\u003cp\u003eAfter years of wrestling with OpenVPN configs, WireGuard feels like a breath of fresh air:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePerformance\u003c/strong\u003e: Runs in kernel space, significantly less overhead\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSimplicity\u003c/strong\u003e: A single config file per interface\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eModern cryptography\u003c/strong\u003e: ChaCha20, Curve25519, BLAKE2s — no cipher negotiation headaches\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSmall codebase\u003c/strong\u003e: ~4,000 lines of code vs. OpenVPN\u0026rsquo;s ~100,000+\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"installation\"\u003eInstallation\u003c/h2\u003e\n\u003cp\u003eOn Debian 12 (Bookworm), WireGuard is available directly:\u003c/p\u003e","title":"Getting Started with WireGuard on Debian 12"},{"content":"Hi, I\u0026rsquo;m Du Song — a software engineer based in China. This blog is where I keep notes on Linux administration, networking, and running self-hosted services.\nMost posts here are written as reference material for myself, but hopefully someone else finds them useful too.\nWhat I Write About Linux server management (Debian, Ubuntu) Networking and DNS configuration Docker and containerized deployments Performance tuning and monitoring Miscellaneous tech explorations Contact Feel free to reach out via GitHub.\n","permalink":"https://blog.dusong.ai/about/","summary":"About this blog","title":"About"}]