Replace $200/month in SaaS subscriptions with a $5 VPS.
By DevToolKit — Built by devs, for devs.
The SaaS tax is real. Here’s what a typical developer pays monthly:
| Service | SaaS Price | Self-Hosted Cost |
|---|---|---|
| Uptime monitoring (UptimeRobot Pro) | $7/mo | $0 |
| Website change detection (Visualping) | $29/mo | $0 |
| SSL monitoring (multiple domains) | $15/mo | $0 |
| SEO analysis tools | $30/mo | $0 |
| DNS/WHOIS lookups | Pay-per-query | $0 |
| Email validation API | $25/mo | $0 |
| URL shortener (Bitly Pro) | $35/mo | $0 |
| Crypto price alerts | $10/mo | $0 |
| Total | $151/mo | $5/mo VPS |
That’s $1,752/year you’re throwing away.
The objection is always “but my time is worth more than the setup.” Fair. That’s why this guide exists — every service below takes under 30 minutes to deploy, and you’ll never touch it again.
For most developers, here’s what you need:
Oracle offers an absurdly generous free tier: - 4 Ampere ARM cores (equivalent to ~8 x86 cores) - 24 GB RAM - 200 GB storage - Forever free (not a trial)
The catch? ARM architecture means some software needs recompiling. But Node.js, Python, Go, and Docker all work perfectly.
Pro tip: Sign up with a non-Gmail address. Corporate/custom domains have higher approval rates.
Ubuntu 24.04 LTS — boring, stable, everything works. Don’t be clever with Arch or NixOS for a server you want to forget about.
# 1. Update everything
sudo apt update && sudo apt upgrade -y
# 2. Create a non-root user
adduser deploy
usermod -aG sudo deploy
# 3. Set up SSH key auth (from your local machine)
ssh-copy-id deploy@your-server-ip
# 4. Disable password auth
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# 5. Set up firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
# 6. Install essentials
sudo apt install -y nginx certbot python3-certbot-nginx nodejs npm git htop
# 7. Set up automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgradesTime: ~10 minutes. You now have a secure server with nginx, Node.js, and auto-updates.
Replaces: UptimeRobot ($7/mo), Pingdom ($15/mo)
// uptime-monitor.js — checks every 60 seconds, alerts via webhook
const https = require('https');
const http = require('http');
const SITES = [
{ url: 'https://yoursite.com', name: 'Main Site' },
{ url: 'https://api.yoursite.com/health', name: 'API' },
];
const WEBHOOK = process.env.ALERT_WEBHOOK; // Discord/Slack webhook
async function check(site) {
return new Promise((resolve) => {
const mod = site.url.startsWith('https') ? https : http;
const start = Date.now();
const req = mod.get(site.url, (res) => {
resolve({
up: res.statusCode < 400,
status: res.statusCode,
ms: Date.now() - start
});
});
req.on('error', () => resolve({ up: false, status: 0, ms: 0 }));
req.setTimeout(10000, () => { req.destroy(); resolve({ up: false, status: 0, ms: 0 }); });
});
}
async function checkAll() {
for (const site of SITES) {
const result = await check(site);
if (!result.up) {
console.log(`🔴 DOWN: ${site.name} (${result.status})`);
if (WEBHOOK) {
// Send alert to Discord/Slack
const payload = JSON.stringify({
content: `🔴 **${site.name}** is DOWN! Status: ${result.status}`
});
// ... webhook POST
}
} else {
console.log(`🟢 UP: ${site.name} (${result.ms}ms)`);
}
}
}
setInterval(checkAll, 60000);
checkAll();Run with: node uptime-monitor.js or better, use PM2:
pm2 start uptime-monitor.js
Replaces: SSLMate ($15/mo), manual checking
#!/bin/bash
# ssl-check.sh — checks cert expiry, alerts if < 14 days
DOMAINS=("yoursite.com" "api.yoursite.com" "blog.yoursite.com")
for domain in "${DOMAINS[@]}"; do
expiry=$(echo | openssl s_client -connect "$domain:443" 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$expiry" ]; then
echo "⚠️ $domain: Could not check SSL"
continue
fi
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ $days_left -lt 14 ]; then
echo "🔴 $domain: SSL expires in $days_left days!"
else
echo "🟢 $domain: SSL OK ($days_left days left)"
fi
doneAdd to crontab: 0 9 * * * /home/deploy/ssl-check.sh
Replaces: Visualping ($29/mo), ChangeTower ($15/mo)
// page-watcher.js — detects content changes on any webpage
const crypto = require('crypto');
const https = require('https');
const fs = require('fs');
const WATCHED = [
{ url: 'https://competitor.com/pricing', name: 'Competitor Pricing' },
{ url: 'https://jobs.company.com', name: 'Job Board' },
];
const STATE_FILE = './page-hashes.json';
const state = fs.existsSync(STATE_FILE) ? JSON.parse(fs.readFileSync(STATE_FILE)) : {};
async function fetchPage(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
async function checkChanges() {
for (const page of WATCHED) {
try {
const content = await fetchPage(page.url);
const hash = crypto.createHash('md5').update(content).digest('hex');
if (state[page.url] && state[page.url] !== hash) {
console.log(`📝 CHANGED: ${page.name}`);
// Alert via webhook, email, etc.
}
state[page.url] = hash;
} catch (e) {
console.log(`❌ Error checking ${page.name}: ${e.message}`);
}
}
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}
setInterval(checkChanges, 3600000); // Every hour
checkChanges();Replaces: DomainTools ($99/mo), SecurityTrails API ($50/mo)
const dns = require('dns').promises;
const { exec } = require('child_process');
async function dnsLookup(domain) {
const [a, aaaa, mx, ns, txt, cname] = await Promise.allSettled([
dns.resolve(domain, 'A'),
dns.resolve(domain, 'AAAA'),
dns.resolve(domain, 'MX'),
dns.resolve(domain, 'NS'),
dns.resolve(domain, 'TXT'),
dns.resolve(domain, 'CNAME'),
]);
return {
A: a.status === 'fulfilled' ? a.value : [],
AAAA: aaaa.status === 'fulfilled' ? aaaa.value : [],
MX: mx.status === 'fulfilled' ? mx.value : [],
NS: ns.status === 'fulfilled' ? ns.value : [],
TXT: txt.status === 'fulfilled' ? txt.value : [],
CNAME: cname.status === 'fulfilled' ? cname.value : [],
};
}
// Usage
dnsLookup('example.com').then(console.log);Replaces: Ahrefs Site Audit ($99/mo), SEMrush ($130/mo)
Not a full replacement, but for quick on-page SEO analysis:
const https = require('https');
const { JSDOM } = require('jsdom');
async function analyzeSEO(url) {
const html = await fetchPage(url);
const dom = new JSDOM(html);
const doc = dom.window.document;
const title = doc.querySelector('title')?.textContent || '';
const meta_desc = doc.querySelector('meta[name="description"]')?.content || '';
const h1s = [...doc.querySelectorAll('h1')].map(h => h.textContent);
const images = [...doc.querySelectorAll('img')];
const imgsWithoutAlt = images.filter(i => !i.alt);
return {
title: { text: title, length: title.length, good: title.length >= 30 && title.length <= 60 },
description: { text: meta_desc, length: meta_desc.length, good: meta_desc.length >= 120 && meta_desc.length <= 160 },
h1: { count: h1s.length, texts: h1s, good: h1s.length === 1 },
images: { total: images.length, missingAlt: imgsWithoutAlt.length },
score: calculateScore(title, meta_desc, h1s, images, imgsWithoutAlt),
};
}Every service should run under PM2:
npm install -g pm2
# Start a service
pm2 start uptime-monitor.js --name uptime
# Auto-restart on crash
pm2 startup
pm2 save
# View status
pm2 status
pm2 logs uptime# Quick system health check
#!/bin/bash
echo "=== CPU ==="
top -bn1 | head -5
echo "=== Memory ==="
free -h
echo "=== Disk ==="
df -h /
echo "=== Services ==="
pm2 jlist | python3 -c "
import json, sys
for p in json.load(sys.stdin):
status = '🟢' if p['pm2_env']['status'] == 'online' else '🔴'
print(f\"{status} {p['name']}: {p['pm2_env']['status']} (restarts: {p['pm2_env']['restart_time']})\")"# 1. Fail2ban — blocks brute force SSH attempts
sudo apt install fail2ban
sudo systemctl enable fail2ban
# 2. Change SSH port (optional but effective)
sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
sudo ufw allow 2222/tcp
sudo systemctl restart sshd
# 3. Disable root login
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
# 4. Set up Let's Encrypt SSL
sudo certbot --nginx -d yourserver.comRun all services on localhost and proxy through nginx:
server {
listen 80;
server_name api.yourserver.com;
location / {
proxy_pass http://127.0.0.1:3456;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
The 3-2-1 rule: 3 copies, 2 different media, 1 offsite.
#!/bin/bash
# backup.sh — daily backup to object storage
BACKUP_DIR="/tmp/backup-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Backup configs
cp -r /etc/nginx/sites-enabled "$BACKUP_DIR/"
cp -r /home/deploy/services "$BACKUP_DIR/"
# Backup databases (if any)
# pg_dump mydb > "$BACKUP_DIR/mydb.sql"
# Compress
tar czf "/tmp/backup-$(date +%Y%m%d).tar.gz" "$BACKUP_DIR"
# Upload to object storage (Backblaze B2 = $0.005/GB/mo)
# b2 upload-file mybucket "/tmp/backup-$(date +%Y%m%d).tar.gz" "backups/"
# Clean up local
rm -rf "$BACKUP_DIR" "/tmp/backup-$(date +%Y%m%d).tar.gz"| Item | Monthly Cost |
|---|---|
| Hetzner CX22 VPS | $4.50 |
| Domain (optional, .dev) | $1.00 |
| Backblaze B2 backups | $0.05 |
| Total | $5.55/mo |
SaaS equivalent of services running: $150-300/mo
Annual savings: $1,740 - $3,534
Time investment: 2-3 hours initial setup, ~15 min/month maintenance
All available right now at http://5.78.129.127:3456:
GET /email/validate/:email — Email validation (syntax +
MX)GET /seo/analyze?url= — Full SEO analysis with
scoringGET /screenshot?url= — Website screenshotsGET /qr/generate?text= — QR code generationGET /dns/lookup/:domain — Complete DNS lookupGET /whois/:domain — WHOIS domain informationGET /ssl/:domain — SSL certificate detailsGET /speed?url= — Website speed test (TTFB, size)GET /headers/inspect?url= — HTTP header security
auditGET /ip/info — IP geolocationGET /hash?text=&algo= — Text hashing
(md5/sha1/sha256/sha512)GET /uuid?count= — UUID v4 generationGET /timestamp — Current timestampsPOST /text/analyze — Text analysis (word count,
sentiment)POST /shorten — URL shortener with analyticsGET /color/:hex — Color conversion (hex→rgb/hsl)POST /json/validate — JSON formatter &
validatorGET /github/trending — GitHub trending reposGET /jobs/remote?search= — Remote job listingsGET /tools — Web-based developer tools UIGET /seo-checker — Interactive SEO checker UIRate limit: 100 requests/minute per IP. No API key needed.
Built and maintained by DevToolKit — an experiment in replacing SaaS with self-hosted alternatives.
All code is open and all APIs are free. If you found this guide
useful, consider sending a Lightning tip:
devtoolkit@coinos.io
Follow on Nostr:
npub1ls9940xh8gtmlt046hnx3vssy5jrumee8j5gxznkxk3swz37c39sxjfzfp
© 2026 DevToolKit. This guide is free to share. Attribution appreciated.