<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: sarvesh kumar</title>
    <description>The latest articles on DEV Community by sarvesh kumar (@ceo_on_bus).</description>
    <link>https://dev.to/ceo_on_bus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3612029%2F2e460c8d-2216-4901-a238-d19c367a19e7.jpg</url>
      <title>DEV Community: sarvesh kumar</title>
      <link>https://dev.to/ceo_on_bus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ceo_on_bus"/>
    <language>en</language>
    <item>
      <title>The Solo Dev's Zero-Trust VPS Stack: How to Make Your Server Invisible to Hackers</title>
      <dc:creator>sarvesh kumar</dc:creator>
      <pubDate>Thu, 28 May 2026 08:46:02 +0000</pubDate>
      <link>https://dev.to/ceo_on_bus/the-solo-devs-zero-trust-vps-stack-how-to-make-your-server-invisible-to-hackers-4of1</link>
      <guid>https://dev.to/ceo_on_bus/the-solo-devs-zero-trust-vps-stack-how-to-make-your-server-invisible-to-hackers-4of1</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Block every incoming port. Use Tailscale as your private mesh network. Move SSH to a non-standard port above 1000, disable root and password auth, and lock it down with SSH keys. Add fail2ban for automatic IP bans. Deploy honeypots on common ports to trap and log attackers. Use Cloudflare Tunnel for any public web traffic. Your server stays reachable only from your devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The old way is suicide&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Most solo developers set up a VPS like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Buy server&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable root login with password&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSH in on port 22&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start developing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maybe install UFW if you’re feeling fancy&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is not a server. That is a target.&lt;/p&gt;

&lt;p&gt;When OVH deleted my VPS after it got compromised, I learned the question isn’t &lt;em&gt;“how do I secure my server?”&lt;/em&gt; The question is: &lt;strong&gt;“how do I remove my server from the public internet entirely?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wrote the full story of how &lt;a href="https://reetlab.substack.com/p/ovh-deleted-my-vps-in-4-days-what" rel="noopener noreferrer"&gt;OVH deleted my VPS in 4 days here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the stack I rebuilt on, piece by piece, with the exact logic behind each layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foohbkobof6vwr72rjfep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foohbkobof6vwr72rjfep.png" alt="Your devices connect through Tailscale. The public internet sees nothing" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 1: The mesh (Tailscale)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A friend pointed me to &lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;. I assumed it was a paid enterprise tool. It’s not. The solo-dev plan is free and powerful enough to run your entire infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Tailscale creates a private mesh network (a WireGuard-based overlay) between your devices. Your laptop, your phone, your VPS, and your home server all talk to each other as if they’re on the same LAN. To the rest of the internet, your server is a black hole.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The philosophy:&lt;/strong&gt; Your VPS doesn’t need to be “reachable” from the internet. It only needs to be reachable from &lt;em&gt;your&lt;/em&gt; devices.&lt;/p&gt;

&lt;p&gt;I installed Tailscale on my new server and on my local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the server (Debian/Ubuntu)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up

&lt;span class="c"&gt;# Copy the Tailscale IP shown (e.g., 100.x.x.x)&lt;/span&gt;
tailscale ip &lt;span class="nt"&gt;-4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I did something that felt illegal: &lt;strong&gt;I blocked all incoming ports.&lt;/strong&gt; Even 22. Even 443. The server has no public-facing doors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Block everything incoming with UFW&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default allow outgoing
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from 100.64.0.0/10 &lt;span class="c"&gt;# Allow Tailscale mesh network only&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I access everything through Tailscale’s private IP. SSH, web servers, and APIs all route over the mesh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one exception:&lt;/strong&gt; I left one non-standard SSH port open as a backup. Not port 22. Something higher. If Tailscale’s coordination server ever goes down, I have a manual fire escape. But that port is locked down by everything that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 2: The moving target (non-standard SSH)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Port 22 is where every automated bot on earth knocks first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Move SSH to a port above 1000. Pick something random. Don’t tell anyone. Update your SSH config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/ssh/sshd_config
&lt;/span&gt;&lt;span class="n"&gt;Port&lt;/span&gt; &lt;span class="m"&gt;2222&lt;/span&gt; &lt;span class="c"&gt;# or whatever you chose
&lt;/span&gt;&lt;span class="n"&gt;PermitRootLogin&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command to restart&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the script-kiddie bots scanning port 22 hit a closed door.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 3: The bouncer (fail2ban)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Even on a hidden port, someone will probe. fail2ban watches your logs and bans IPs that fail repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; If an IP tries to brute-force SSH (or any other service) and fails 3 times, fail2ban adds them to an IPTables ban list. Silent. Automatic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;/etc/fail2ban/jail.local :&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sshd]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2222 # Your non-standard SSH port&lt;/span&gt;
&lt;span class="py"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sshd&lt;/span&gt;
&lt;span class="py"&gt;logpath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/auth.log&lt;/span&gt;
&lt;span class="py"&gt;maxretry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;bantime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;86400 # 24 hours&lt;/span&gt;
&lt;span class="py"&gt;findtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;600 # 10 minute window&lt;/span&gt;

&lt;span class="err"&gt;sudo&lt;/span&gt; &lt;span class="err"&gt;systemctl&lt;/span&gt; &lt;span class="err"&gt;restart&lt;/span&gt; &lt;span class="err"&gt;fail2ban&lt;/span&gt;
&lt;span class="err"&gt;sudo&lt;/span&gt; &lt;span class="err"&gt;fail2ban-client&lt;/span&gt; &lt;span class="err"&gt;status&lt;/span&gt; &lt;span class="err"&gt;sshd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failed login attempts don’t just fail; they get the IP jailed for 24 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 4: No passwords, no root&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This was the hardest habit to break. I was used to &lt;code&gt;root&lt;/code&gt; + password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a normal user with sudo
&lt;/h3&gt;

&lt;p&gt;Never log in as root. Create a dedicated user and give it sudo privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser sarvish
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;myname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Copy your SSH public key to the server
&lt;/h3&gt;

&lt;p&gt;Run this from your &lt;em&gt;local machine&lt;/em&gt; to push your key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;YOUR_PORT] sarvish@[TAILSCALE_IP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Lock down SSH
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; on the server to disable root and password auth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;PermitRootLogin&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;

&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;systemctl&lt;/span&gt; &lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="n"&gt;sshd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your key isn’t on the server, you are not getting in. Period.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 5: The honey trap (honeypot)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A server friend told me: &lt;em&gt;“Install a honeypot on frequently attacked ports.”&lt;/em&gt; I had never considered this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A honeypot like &lt;code&gt;cowrie&lt;/code&gt; or &lt;code&gt;ssh-honeypot&lt;/code&gt; opens fake ports that look like real services. When a hacker scans you and tries to exploit these decoy ports, the honeypot logs everything, wastes their time, and depending on your setup can trigger an immediate IP ban.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Attackers don’t know which ports are real and which are traps. They touch a honeypot port, and they’ve already revealed themselves as malicious before they even get near your actual services.&lt;/p&gt;

&lt;p&gt;I installed a lightweight honeypot called &lt;code&gt;ssh-honeypot&lt;/code&gt; on a few common ports:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install build dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;build-essential libssh-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Clone and build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/droberson/ssh-honeypot.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ssh-honeypot
make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Generate an RSA key for the honeypot
&lt;/h3&gt;

&lt;p&gt;The honeypot needs its own SSH host key to look like a real server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-b&lt;/span&gt; 2048 &lt;span class="nt"&gt;-f&lt;/span&gt; ssh-honeypot.rsa &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Run the honeypot on a decoy port
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./ssh-honeypot &lt;span class="nt"&gt;-p&lt;/span&gt; 2223 &lt;span class="nt"&gt;-r&lt;/span&gt; ssh-honeypot.rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why port 2223?&lt;/strong&gt; It looks like an alternative SSH port. Bots scanning for SSH will hit it, try to log in, and get trapped while the honeypot logs every command they attempt.&lt;/p&gt;

&lt;p&gt;Now my server isn’t just hidden; it’s actively baiting and trapping anyone who comes knocking.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Layer 6: Cloudflare Tunnel (for web traffic)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If I need to expose a web app or API, I don’t open port 443 on the server.&lt;/p&gt;

&lt;p&gt;I use &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; (&lt;code&gt;cloudflared&lt;/code&gt;). It creates an outbound-only connection from my server to Cloudflare’s edge. The public internet talks to Cloudflare. Cloudflare talks to my server through the tunnel. My server never accepts a direct inbound connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install cloudflared&lt;/span&gt;
wget &lt;span class="nt"&gt;-q&lt;/span&gt; https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; cloudflared-linux-amd64.deb

&lt;span class="c"&gt;# Authenticate (opens browser)&lt;/span&gt;
cloudflared tunnel login

&lt;span class="c"&gt;# Create and run tunnel&lt;/span&gt;
cloudflared tunnel create myapp
cloudflared tunnel route dns myapp myapp.mydomain.com
cloudflared tunnel run myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get zero open ports, zero DDoS exposure, and zero certificate management headaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What I’m using now&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I moved to a European server. Day 2, and it’s running smooth. The difference isn’t the provider. The difference is the architecture.&lt;/p&gt;

&lt;p&gt;Everything sits behind Tailscale. SSH is on a ghost port with key-only auth. fail2ban watches the logs. The honeypot is catching probes. Cloudflare handles public traffic. I don’t fear port scans anymore because, to the public internet, my server doesn’t exist.&lt;/p&gt;

&lt;p&gt;It took one catastrophic deletion to learn this. You don’t need to lose your server to get it right.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>tailscale</category>
      <category>devops</category>
      <category>vps</category>
    </item>
  </channel>
</rss>
