Linux VPS Security Hardening India 2026
The default Ubuntu 22.04 VPS you just provisioned is not secure. Here's how to harden it properly — SSH keys, UFW, fail2ban, kernel tweaks, and more — in 45 minutes.
The default Ubuntu 22.04 VPS you just provisioned is not secure. Root login is enabled. Password authentication is on. Every port scanner on the internet has already found your IP and is trying combinations of root/root, root/toor, root/123456, and about ten thousand other guesses. By the time you finish reading this sentence, a bot in Eastern Europe has attempted to log in at least twice.
Here's how to fix that in 45 minutes.
This guide is written for the Indian developer or startup sysadmin who just spun up a VPS — whether it's on Inservers, DigitalOcean, Hetzner, or anywhere else — and wants to do this right before putting anything real on it. You're comfortable with SSH. You've heard of fail2ban. You haven't heard of tcp_syncookies. By the end, you will have — and you'll understand exactly why it matters.
No comparison tables. No affiliate fluff. Just the commands and the reasoning.
First 5 Minutes: Disable Root and Set Up SSH Keys
This is the single highest-impact thing you can do. Do it before anything else.
Generate a Key Pair Locally
On your local machine (not the server):
ssh-keygen -t ed25519 -C "[email protected]"
Use ed25519. Not RSA 2048. Ed25519 is faster, produces shorter keys, is mathematically stronger, and is supported everywhere that matters. If someone tells you to generate RSA keys in 2026, ask them why. There's no good answer.
Set a passphrase. Yes, it's slightly more friction every time you connect. That friction is protecting you.
Copy Your Public Key to the Server
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@YOUR_SERVER_IP
Create a Non-Root Sudo User
Never run production workloads as root. Create a separate user:
adduser deploy
usermod -aG sudo deploy
Copy your authorized key to the new user:
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
Test that you can SSH in as deploy before proceeding. If you lock yourself out now, you'll need your provider's recovery console.
Lock Down SSH Configuration
Edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
Protocol 2
Port 2222
LoginGraceTime 30
MaxAuthTries 3
Changing the SSH port to something like 2222 is not real security — a port scan finds it in seconds. But it cuts your auth.log noise by 90% because most automated scanners only try port 22. It's a quality-of-life change, not a security guarantee.
Restart SSH — but keep your current session open until you've verified the new configuration works:
systemctl restart sshd
Open a new terminal window and test: ssh -p 2222 deploy@YOUR_SERVER_IP. Only close the original session after you can confirm the new connection works.
The India reality check: a large share of Indian developers skip SSH keys on personal projects because "it's just an MVP" or "no one cares about my server." Attackers run automated scripts that care about every server on the internet. A VPS with root password auth enabled receives roughly 1,500 login attempts per day from automated scanners. If your password has fewer than 20 truly random characters, it's a question of when, not if. One weak password later and your server is mining Monero, sending spam, or acting as a pivot point in someone else's attack — while you're wondering why your server keeps hitting CPU limits.
UFW: Simple Firewall That Actually Gets Configured
iptables is powerful. It's also enough rope to hang yourself with if you're not a networking expert. UFW (Uncomplicated Firewall) wraps iptables in a sane interface, and for 95% of VPS workloads it's exactly the right tool.
apt install ufw
Set your defaults first:
ufw default deny incoming
ufw default allow outgoing
Then allow only the ports you actually need:
ufw allow 2222/tcp # SSH on your new port
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
Enable it:
ufw enable
ufw status verbose
The status verbose output is worth reading. It shows exactly what's allowed, in what direction, on what interface. If you see a port open that you didn't add, investigate immediately.
If you're running a database like MySQL or PostgreSQL: do not open port 3306 or 5432 to the internet. Your application connects to the database on localhost. If you absolutely need remote database access, open it only to a specific IP: ufw allow from 203.0.113.15 to any port 5432.
A note on what UFW is not doing for you: If you're on Inservers, you're behind Cloudflare Magic Transit — 500 Tbps of volumetric DDoS protection that absorbs SYN floods, UDP floods, and amplification attacks before they even reach your server. That means you don't need iptables rate-limiting rules for network-layer DDoS. Magic Transit handles that upstream at the network edge. UFW's job, in that context, is purely access control: deciding which ports and protocols reach your application. On a budget provider without upstream volumetric protection, you'd additionally need rules like iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT to survive a SYN flood. On a properly provisioned setup with upstream DDoS protection, that's handled for you.
fail2ban: Automatic IP Banning for Repeated Login Failures
UFW blocks ports. fail2ban watches your logs and bans IPs that are hammering those ports with failed authentication attempts.
apt install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Always work from jail.local. The original jail.conf gets overwritten on package upgrades.
Edit /etc/fail2ban/jail.local. Find the [DEFAULT] section and set:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
Then configure the SSH jail specifically — override the defaults for SSH because you want it stricter:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
Three failed attempts and they're banned for 24 hours. That's not too aggressive for legitimate users who might mistype once or twice, and it's severe enough to make credential stuffing attacks completely impractical.
Start and enable it:
systemctl enable fail2ban
systemctl start fail2ban
Check it's working:
fail2ban-client status sshd
Real talk: if you enable this on a fresh VPS and check back after two hours, you'll likely see dozens of IPs already banned. They were there before you enabled fail2ban. You just weren't looking. Those bots didn't stop when you weren't watching — they just weren't being stopped.
To unban a legitimate IP (for when you accidentally lock yourself out):
fail2ban-client set sshd unbanip YOUR_IP
Automatic Security Updates: Set It and Verify It
Not all automatic updates are wise — blindly auto-updating every package is how you break a production web application at 2 AM. But security-only updates for the OS? You should be running those automatically.
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
Then edit /etc/apt/apt.conf.d/50unattended-upgrades. You want security updates on and everything else off by default:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Make sure the regular ${distro_id}:${distro_codename}-updates line is commented out. Enable the service:
systemctl enable unattended-upgrades
systemctl start unattended-upgrades
Verify it ran:
cat /var/log/unattended-upgrades/unattended-upgrades.log
Set email notifications so you know when updates are applied: in the same file, uncomment and fill in Unattended-Upgrade::Mail "[email protected]";.
Kernel Hardening via sysctl
This is where most security guides stop. It's also where most production Linux servers are left unnecessarily exposed.
The Linux kernel has dozens of network and system settings that are off by default for historical compatibility reasons, not because leaving them off is a good idea. You change them via sysctl.
Create /etc/sysctl.d/99-security.conf:
# SYN flood protection using SYN cookies
net.ipv4.tcp_syncookies = 1
# Reject packets claiming to be from our own addresses
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Don't accept ICMP redirects (prevents MITM attacks on routing)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Don't send ICMP redirects (we're not a router)
net.ipv4.conf.all.send_redirects = 0
# Ignore ICMP echo requests sent to broadcast addresses
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Ignore bogus ICMP error responses
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Log packets with impossible addresses
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Address Space Layout Randomization — makes buffer overflows harder to exploit
kernel.randomize_va_space = 2
# Prevent hard/symlink abuse in world-writable directories
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
# Restrict kernel pointer exposure
kernel.kptr_restrict = 1
# Restrict access to kernel logs
kernel.dmesg_restrict = 1
Apply immediately without rebooting:
sysctl -p /etc/sysctl.d/99-security.conf
These settings survive reboots because the file lives in /etc/sysctl.d/. A quick explanation of the important ones:
tcp_syncookies = 1: When the SYN queue fills up (during a SYN flood), the kernel sends a cryptographic cookie instead of a half-open connection. Legitimate clients complete the handshake anyway; flood packets are dropped without consuming resources. On Inservers, Magic Transit handles volumetric SYN floods before they reach your kernel — but tcp_syncookies is still worth having as a secondary defence for lower-volume targeted attacks.randomize_va_space = 2: Enables full ASLR. Without this, an attacker who triggers a buffer overflow knows exactly where in memory to jump. With it, the stack, heap, and library addresses are randomised on every execution.rp_filter = 1: Reverse path filtering. If a packet claims to come from an address that your server would not normally route through the incoming interface, it gets dropped. This stops spoofed-source-IP attacks.
Audit with Lynis
Once you've applied hardening, you need something to tell you what you missed. Lynis is an open-source security auditing tool that runs a comprehensive check against the CIS benchmark and other security standards.
apt install lynis
lynis audit system
Lynis produces a hardening index out of 100. A fresh, unmodified Ubuntu 22.04 VPS typically scores between 55 and 65. After applying the steps in this guide, you should be in the 75-85 range. Above 80 is solid for a production VPS.
Read the output with some judgment. Lynis will flag a lot of things. Not all of them matter equally:
- HIGH severity findings: fix these. Writable /tmp without sticky bit, SUID binaries in unexpected places, world-readable /etc/passwd shadow entries, old SSH protocol versions — these are real vulnerabilities.
- MEDIUM severity: evaluate case by case. Some are valid; some are hardening suggestions that would break your specific application.
- LOW severity and suggestions: these are often noise. Lynis will suggest disabling the CUPS printer daemon on a VPS that has never had and never will have a printer attached. It will suggest adding
/etc/issue.netbanner text. These are technically valid but irrelevant for most VPS workloads.
Common findings on Indian VPS setups: - /tmp world-writable without sticky bit — should be 1777. Check with ls -la / | grep tmp. Fix with chmod 1777 /tmp. - No sudo timeout — by default, sudo remembers your authentication for 15 minutes. Add Defaults timestamp_timeout=5 to /etc/sudoers via visudo. - Missing login banner — add something to /etc/issue.net if you want, but it won't protect you. - Core dumps enabled — ulimit -c 0 in /etc/security/limits.conf disables them.
SUID Binary Audit
SUID (Set User ID) binaries run with the permissions of their owner, regardless of who executes them. A handful of legitimate SUID binaries exist on every Linux system — ping, sudo, passwd — but unexpected ones are a serious red flag.
List all SUID binaries on your system:
find / -perm /4000 -type f 2>/dev/null
Compare the output against a known-good list for Ubuntu 22.04. The typical expected binaries include things like /usr/bin/sudo, /usr/bin/passwd, /usr/bin/newgrp, /usr/bin/gpasswd, /usr/bin/chsh, /usr/bin/chfn, /usr/sbin/pppd, /bin/ping, /bin/mount, /bin/umount, and /usr/bin/pkexec.
Anything else warrants investigation. An unexpected SUID binary — especially one in /tmp, /var, or a web root directory — is a strong indicator of compromise.
For /tmp permissions:
ls -la / | grep tmp
You want to see drwxrwxrwt — that trailing t is the sticky bit, which means only the file's owner can delete their own files from that directory. Without it, any user can delete any other user's files from /tmp.
Log Monitoring with Logwatch
You have logs. You are not reading them. Logwatch fixes this by sending you a daily email summary of everything notable that happened on your server.
apt install logwatch
Test it manually first:
logwatch --output stdout --detail high
Read through the output. You'll see SSH login attempts, sudo usage, failed services, and more. Once you've confirmed it looks right, set it up as a daily cron job. Create /etc/cron.daily/00logwatch:
#!/bin/bash
/usr/sbin/logwatch --output mail --mailto [email protected] --detail high
Make it executable:
chmod +x /etc/cron.daily/00logwatch
For Indian developers who've never done this: the first few daily emails will be eye-opening. You'll discover your server is more interesting to the internet than you thought. Hundreds of SSH attempts, automated web scanner requests, and probe traffic for common vulnerabilities — all logged, all summarised. After the first week it becomes routine background noise. But if one day your email shows something unusual — a new user account, a sudo escalation you didn't perform, a process you don't recognise — you catch it in the morning digest rather than weeks later.
What Inservers' Magic Transit Actually Covers (and What It Doesn't)
This is worth being precise about because it changes what you need to configure yourself.
Inservers runs Cloudflare Magic Transit — 500 Tbps of network-level DDoS protection. This operates at layer 3 and layer 4 of the network stack. What that means in practice: if someone launches a UDP flood, a SYN flood, or an ICMP amplification attack at your VPS, Magic Transit absorbs it at Cloudflare's edge before a single packet reaches your server. Your server never sees the attack traffic. This is a significant operational advantage — most VPS providers at this price point leave you to absorb attacks yourself or get null-routed.
The implication for your configuration: you do not need to write iptables rules to defend against volumetric floods. On a provider without this protection, you'd need something like:
iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
On Inservers, you can skip that — Magic Transit handles it upstream.
What Magic Transit does not protect, and what remains entirely your responsibility:
Application-layer vulnerabilities. SQL injection, XSS, command injection in your code — these are HTTP requests that look legitimate to a network filter. Magic Transit passes them straight through, because they're not volumetric traffic. Your application code, input validation, and WAF configuration are your problem.
Weak SSH credentials. A brute-force login attempt is one packet every few seconds — it's not a flood, it bypasses network DDoS filters, and it works if your password is weak. SSH key authentication and fail2ban are the answers here.
Misconfigured services. An unpatched Nginx, an exposed Redis port, a world-readable .env file — none of these are network attacks and none of them are protected by upstream DDoS mitigation. The division of responsibility is clear: the provider handles the network layer, you handle everything above it.
This is the right model. Expecting your hosting provider to protect against application vulnerabilities is like expecting a power company to protect against electrical fires inside your house. They deliver the power clean; what you do with it is your job.
Quick Verification Checklist
Run through this after completing the guide. If you can't tick something, go back and fix it before pointing DNS at this server.
- [ ] SSH key pair generated with ed25519
- [ ] Public key added to non-root user's
authorized_keys - [ ]
PermitRootLogin noconfirmed in/etc/ssh/sshd_config - [ ]
PasswordAuthentication noconfirmed in/etc/ssh/sshd_config - [ ] SSH port changed from 22
- [ ] Verified new SSH login works before closing old session
- [ ] UFW active with
ufw status verboseshowing only needed ports open - [ ] Default UFW policy is deny incoming
- [ ] fail2ban running:
systemctl status fail2banshows active - [ ] fail2ban SSH jail configured with maxretry 3 or fewer
- [ ] Unattended security upgrades enabled and verified in logs
- [ ]
/etc/sysctl.d/99-security.confcreated and applied - [ ]
sysctl net.ipv4.tcp_syncookiesreturns 1 - [ ] Lynis score above 70 (above 80 is the target)
- [ ] SUID audit run — no unexpected binaries
- [ ]
/tmppermissions are 1777 (sticky bit set) - [ ] Logwatch configured with working email delivery
FAQ
Is Inservers' DDoS protection a replacement for UFW?
No — they operate at completely different layers. Magic Transit filters volumetric network attacks before they reach your server. UFW controls which ports and protocols are allowed to reach your applications after packets arrive. You need both. Removing UFW because you have upstream DDoS protection is like removing your house's front door because the neighbourhood has good security cameras.
Do I need a server firewall if I'm behind Cloudflare's CDN?
Yes. Cloudflare CDN (for web traffic) and network-level DDoS protection are not a server firewall. Cloudflare proxies your HTTP/HTTPS traffic but doesn't know anything about your SSH port, your database port, or whatever else might be listening on your server. If someone finds your server's origin IP — which is possible through various means — they bypass Cloudflare entirely. UFW ensures that even if they connect directly, the only open ports are the ones you've deliberately opened.
How do I know if my VPS has already been compromised?
Look for these specific indicators rather than waiting for something obvious to go wrong:
- Unexpected processes:
ps auxfand compare against what you've installed. A process you don't recognise — especially one running as root or consuming significant CPU — is worth investigating immediately. - Failed and successful auth log:
grep "Accepted" /var/log/auth.logshows every successful login. Any login from an IP you don't recognise is a breach. - Unexpected cron jobs:
crontab -landcrontab -u root -land check/etc/cron*directories. Cryptominers and reverse shells often install themselves here for persistence. - Listening ports:
ss -tulnpornetstat -tulnp— any port listening that you didn't configure means something unexpected is running. - Modified system binaries:
dpkg --verifychecks installed package files against their expected checksums. Replaced system binaries (a classic rootkit technique) show up here.
If you find evidence of compromise on a production server, the correct response is to take a snapshot for forensic analysis, spin up a new clean VPS, migrate your data, and decommission the compromised one. Trying to clean an actively compromised server is unreliable.
Should I use UFW or iptables directly?
UFW for almost everyone. iptables if you have specific requirements: complex NAT rules, traffic shaping, multi-interface routing, or kernel-level packet marking for QoS. If you're asking which one to use and you're not sure what NAT traversal means in a VPN context, use UFW. The operational simplicity advantage of UFW is real, and the capability gap only matters for advanced network configurations that most VPS workloads never need.
How often should I run Lynis?
Minimum: monthly. Mandatory: after any major package installation, kernel upgrade, or significant configuration change. Lynis is not a one-time tool — the hardening landscape changes, your server configuration changes, and a monthly audit catches configuration drift before it becomes a vulnerability. Add it to a monthly calendar reminder and treat a score below 70 as something that needs fixing that week, not eventually.
One More Thing Before You Go Live
Everything in this guide makes your server significantly more secure than it was out of the box. It does not make it invulnerable. Security is an ongoing practice, not a one-time configuration exercise.
The Indian startup ecosystem has had some painful breaches in recent years that trace directly back to servers provisioned as MVPs and never hardened. "We'll fix security before we launch" is a statement that ages badly — especially because attackers don't check your roadmap. The VPS you provisioned this morning is already being probed. The steps above don't take long, and they're dramatically better than doing nothing.
If you're running on Inservers with Ubuntu 22.04 and full root access, you have everything you need to implement this guide exactly as written. Do it before you deploy your application. Do it before you put a domain on it. Do it now, while you have a clean slate and no production traffic to worry about.
The 45 minutes you spend on this is not overhead. It's insurance that actually pays out.