The indie SaaS security stack I run on a $7/mo VPS
When you're a 1-3 person dev shop, you can't afford Snyk and you don't have a security team. You also can't afford to get breached. Here's the full stack I actually run on the $7/mo Hostinger VPS that serves my production SaaS.
This is not a "best practices" article. Every line below corresponds to a config file in production right now.
1. UFW — block everything by default
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Five commands. The single largest reduction in attack surface you can make in 30 seconds. Verify with sudo ufw status verbose.
2. SSH keys only, no passwords
/etc/ssh/sshd_config.d/00-hardening.conf:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
sudo systemctl restart ssh. Now password brute-force attempts hit a wall on the first packet.
3. fail2ban — auto-ban brute-forcers
sudo apt install fail2ban
/etc/fail2ban/jail.local:
[sshd]
enabled = true
maxretry = 3
findtime = 600
bantime = 3600
Three failed SSH attempts in 10 minutes → IP banned for an hour. Check sudo fail2ban-client status sshd after a week — you'll see hundreds of banned IPs. Most of the noise just stops.
4. unattended-upgrades — apt patches without you
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
/etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESM:${distro_codename}";
};
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "ops@yourdomain.com";
I deliberately leave Automatic-Reboot "false" because a 3am reboot during traffic is its own incident. Instead, see step 6.
5. Stripe webhook signature validation
If you take payments, every webhook handler must verify the signature header. Without this, anyone can POST customer.subscription.created events at your endpoint and grant themselves access.
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature')
if (!sig) return new Response('missing signature', { status: 400 })
const body = await req.text()
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
)
} catch {
return new Response('invalid signature', { status: 400 })
}
// ... safe to process
}
Fail closed. If STRIPE_WEBHOOK_SECRET is unset, return 503 — never silently skip the check.
6. Kernel patching with an actual reboot strategy
This is the part most indie shops skip. unattended-upgrades installs the new kernel but won't reboot. The running kernel stays vulnerable.
/usr/local/bin/kernel-patch-check.sh:
#!/usr/bin/env bash
running="$(uname -r)"
installed="$(dpkg -l 'linux-image-*' | awk '/^ii/ {print $2}' | tail -1 | sed 's/linux-image-//')"
if [ "$running" != "$installed" ]; then
echo "Reboot pending: running=$running installed=$installed" \
| mail -s "Kernel patch ready on $(hostname)" ops@yourdomain.com
fi
Cron it hourly. Get an email the moment a new kernel ships and is installed. Then you reboot during your next low-traffic window — manually, with eyes on the systemd units coming back up.
7. The CVE-to-action layer (the missing piece)
Steps 1-6 cover known-class vulnerabilities being delivered via apt. They don't cover the case where:
- A CVE drops for a kernel module (CVE-2026-31431 / "Copy Fail", late April) and no Ubuntu kernel fix is shipped yet — you need a
modprobe blacklistfor the affected module, not an apt update. - A CVE is fixed only in Ubuntu Pro (ESM) and your free Ubuntu install can't see the fix — you need to attach a free Pro token first.
- You want to know within 5 minutes that an advisory landed for your stack, not when the cron mail eventually arrives.
I tried Snyk's free tier. Hit the 200-vulns/mo limit in under a week. Their smallest sales-team-required tier was ~$25K/yr. I tried Vuls.io — works, but I spent more time tuning it than I would have spent reading the advisories myself.
So I built StackPatch for exactly this shape. SSH read-only or small read-only agent. Continuous match against the upstream feeds (USN, DSA, Alpine secdb, OSV.dev for RHEL family). Per-server public audit URL. Exact remediation command per finding — including the modprobe-blacklist case, not just apt upgrade.
$99 lifetime for the first 50 founders, then $19-49/mo. The free quickscan is a one-liner you can curl right now without signing up:
curl -fsSL https://mindsparkstack.com/scan.sh | bash
That'll spit out your kernel + apt package state matched against the live USN feed. No data sent anywhere unless you opt into the waitlist + monitoring at the end.
The honest pitch
This stack — UFW, SSH hardening, fail2ban, unattended-upgrades, kernel patch checker, webhook signature validation, and a CVE-to-action layer — covers most of what a security team would set up for a small Linux fleet. The first six pieces are free and take about an hour. The seventh piece is the gap most indie shops just live with, because the existing tools are either ops-heavy or enterprise-priced.
If you have a more complete stack, the comments are open and I will steal from you. If you're missing pieces, the free quickscan will tell you which ones in 30 seconds.
Top comments (0)