---
title: "My 2025 Lessons: Setting Up Mini IT Infrastructure, Deploying 30+ Live AI Apps & Defending a Bot Attack"
slug: self-hosting-infrastructure-small-business-2025
date_published: 2025-12-27T00:00:00.000Z
original_url: https://www.tigzig.com/post/self-hosting-infrastructure-small-business-2025
source: fresh
processed_at: 2025-12-27T10:00:00.000Z
---

# Building & Deploying AI Apps: Infrastructure Guide (VPS, Security, Monitoring, Costs)

I build AI & automation tools and manage VPS, DNS, security, databases, auth, infra - the full chain. Working setups below - for data scientists, analysts, and small businesses deploying their own tools.
 
30+ live AI & Automation apps. Open source, publicly available on this site [tigzig.com](https://tigzig.com)  


Fourth in my 2025 learnings series on building and deploying AI apps.
full cycle of AI apps from build to live deployment.


---

## STRATEGIC CHOICES  

### Skipped AWS and Azure
Too expensive, too complicated for my needs. These are aircraft carriers. My requirements fit a Toyota.

I work with small businesses. Don't need 'Enterprise Level' or 'At Scale' yet. When that changes, I'll move. Until then, simpler providers work fine.

I keep small AWS/Azure instances running for client testing and compatibility checks. But my production apps run elsewhere.

### Hetzner as primary VPS
Chose Hetzner for clean UI, solid performance, straightforward pricing, simple setup.

Server that I use to host tigzig.com backend - CAX21 (ARM-based)
- 4 vCPUs
- 8GB RAM  
- 80GB disk
- €5.99/month

Currently hosting:
- 30+ FastAPI backends
- 2 instances of n8n
- 2 instances of Coolify
- Still ~30% capacity remaining

---

## SECURITY : SERVER & FRONTEND 

### Defended a bot attack with Fail2ban
A few months after setup, got hit with a bot attack - rotating proxies, thousands of SSH login attempts. Server load spiked, had to restart.

I already had Fail2ban running but with loose parameters. Tightened the config:

Current Fail2ban settings:
- maxretry: 5 attempts
- findtime: 3600 seconds (1 hour window)
- bantime: 86400 seconds (24 hour ban)
- Backend: nftables

Results after tightening:
- Currently banned: 157 IPs
- Total banned since last server restart a week back: 1,223 IPs
- Total failed attempts blocked: 6,082

Attack stopped. No issues since.

### Layered firewall with UFW
Hetzner has built-in firewall. I added UFW (Uncomplicated Firewall) on the server following the video guide, but in hindsight Hetzner firewall alone is sufficient - this was redundant.

UFW config:
- Default incoming: DENY
- Default outgoing: ALLOW  
- Allowed ports: 22 (SSH), 80 (HTTP), 443 (HTTPS)
- Docker bridge networks: allowed (172.17.0.0/16, 172.18.0.0/16)

### SSH hardening
Password auth completely disabled. Root login via SSH keys only.

SSH config:
- PermitRootLogin: prohibit-password
- PasswordAuthentication: no
- PubkeyAuthentication: yes
- KbdInteractiveAuthentication: no

No password = no brute force risk.

### Automatic security updates enabled
unattended-upgrades package installed and active. Security patches apply automatically. One less thing to monitor.

### Three baseline security rules for every backend
1. **CORS**: configured per use case (sometimes loose, sometimes strict - depends on app)
2. **Rate limiting**: SlowAPI middleware, default limits on all endpoints
3. **API keys**: for non-browser access (programmatic clients)

Browser access = CORS check. Non-browser = API key required.  

---

### Frontend Security: API Keys

Environment variables in React get bundled into client build - visible to anyone. Not secure for API keys or backend endpoints.

**Solution: Server-side execution**

**1. Vercel API Routes (for quick tasks <5 min)**  
Serverless functions run server-side. Frontend calls your route, which holds sensitive variables and makes external API calls. Keys stay hidden. 5-minute execution limit on free tier.

**2. Polling (what I use for long tasks)**  
Route initiates job, returns ID. Frontend polls status every 10-15 seconds via separate route. Backend processes however long needed - no timeout. Requires job queue and status endpoints.

**3. Full server-side rendering (client case)**  
One client: PHP with server-side rendering. All processing happens server-side. Browser sees rendered HTML only. No React, no exposure issues.

**4. Self-hosted options (haven't tried)**  
Next.js SSR on Vercel still hits 5-minute limit. Self-hosted Next.js on Coolify - no timeout, continuous Node server. FastAPI on Coolify works similarly.

---

### Authentication: Auth0 currently, exploring Clerk
Some apps need user auth. Using Auth0 now - works but can get complicated with callback URLs and React frontend integration. Exploring Clerk as alternative but haven't tested yet.

---

## DEPLOYMENT & HOSTING  

### Coolify = self-hosted Render
Point to GitHub repo, Coolify deploys each backend in its own Docker container. Seamless. 

Each FastAPI backend gets isolated container, auto-restarts on crash, logs accessible via UI.

### Flowise via docker custom image
Deployed Flowise via a custom docker image built for ARM64 architecture of my Hetzner server, as the official Flowise image was for AMD64 (x86). Manually triggered via Github Actions.

### n8n via docker compose
n8n via a docker compose - UI worked fine through the reverse proxy, but webhooks weren't reachable from outside - external services couldn't callback to my n8n instance. The fix required a few changes to the compose file: 

- Custom DNS - Added dns: 1.1.1.1, 8.8.8.8 to force the container to use external DNS instead of Docker's internal resolver

- Privileged mode - Set privileged: true to give the container elevated network permissions needed for proper routing behind the proxy. 

- Configured WEBHOOK_URL and N8N_EDITOR_BASE_URL environment variables so n8n knows its public URL when generating webhook endpoints. 

- Without these changes, n8n runs but can't communicate with the outside world - a common gotcha when self-hosting behind reverse proxies.

### Vercel for frontends
40+ UIs on Vercel free tier. Fast, reliable, zero config deployments. No reason to self-host static frontends when Vercel does it better.

### My top recommended resource for Hetzner + Coolify setup
This guide walked me through the entire security hardening process:
https://www.youtube.com/watch?v=taJlPG82Ucw

I could have used an AI coder with CLI access now, but when I set this up, this video + ChatGPT got me through it step-by-step.

---

## DNS, DOMAIN & CLOUDFLARE  

### Keep domain registrar separate from DNS
Domains registered on: Namecheap or GoDaddy  
DNS management: Cloudflare

Reason: If Cloudflare has issues, I can quickly point DNS elsewhere. If registrar has issues, DNS still works.

### Migrated 100+ DNS records to Cloudflare
Migration was seamless. Cloudflare lets you import/export DNS record files. Moved everything in one shot.

### DNS management is straightforward
Set routing rules, configure caching, manage SSL - all in one dashboard. Clean UI.

I'm using a fraction of what Cloudflare can do, but what I use works well.

### Cloudflare proxy: 100-second timeout limit
Free tier has 100-second timeout for proxied requests. 

Problem: Some of my backends run 5-10 minutes (heavy data processing, large file uploads).

Solution: Remove those endpoints from Cloudflare proxy, point directly to server. Manage security at FastAPI level (CORS, rate limits, API keys).

Most backends are proxied. Long-running ones are direct.

### Free tier SSL: one subdomain level only
Cloudflare free tier handles SSL for:
- app.tigzig.com ✓
- api.tigzig.com ✓

But NOT for:
- hosting.app.tigzig.com ✗

Multi-level subdomains require paid tier for SSL certificates. Plan accordingly.

### Caching rules for static content
Set up custom cache rules for:
- Blog posts (1 month TTL)
- Large datasets (1 month TTL)

After deploying new blog posts, I run a cache purge + warm script. Purges old cache, warms new content. Users see updates immediately instead of waiting for TTL expiry.

### Cloudflare caching for large files
Some apps serve 1GB+ datasets. Hosting on Hetzner gives decent speed. Hosting via Cloudflare cache gives better speed. Files still live on my server, but Cloudflare edge caching improves delivery.

---

## MONITORING  

Built a custom app for monitoring all my FastAPI backends.

### Frontend: React dashboard
Built simple React dashboard to view:
- API success/failure rates
- Response times
- Endpoint usage

Deployed on Vercel. Pulls from Neon database.

### Created PyPI package for API logging
Built custom FastAPI middleware: `tigzig-api-monitor`

Published to PyPI: https://pypi.org/project/tigzig-api-monitor/

Why a package? I was copying the same logging code across 30+ backends. Package = import once, use everywhere.

### What the package collects
- App name
- Endpoint path (no query params)
- HTTP method
- Response status code
- Response time (milliseconds)
- Client IP (hashed - see next point)
- User-Agent
- Origin
- Referer path (no query params)

### IP hashing = no PII stored
IPs are hashed before storage. Can't reverse-engineer original IP. Privacy by design. 

Geolocation tracking: done separately using GeoLite2 free database. Not very accurate, but works for basic country-level tracking.

### Non-blocking async logging
Logging happens asynchronously. API requests don't wait for log writes. If logging fails, API still works.

### Database: PostgreSQL on Neon
All API logs go to PostgreSQL on Neon.com. 

Neon free tier: 40-50 projects, 500MB each. Perfect for log storage across multiple apps. Currently on paid tier temporarily for higher limits, but free tier worked fine for months.

### Web analytics: PostHog
Migrated from StatCounter to PostHog. 

StatCounter fine for low traffic. With increasing visitors, needed better dashboards and event tracking. 

PostHog setup: cloud version, free tier. Easy to configure, customizable dashboards.

### Brevo for automated emails
Email not a major requirement for my apps. Occasionally need notification emails (user action triggers alert to me). 

Using Brevo free tier. Works fine for low-volume transactional emails. Not using for campaigns or marketing - just automated notifications.

---

## SELF-BUILT TOOLS  

### Blog automation: markdown to HTML pipeline
Used to host blog on managed service. Migrated out. Now self-hosted with custom pipeline.

Process:
1. Write in markdown
2. Run script - script converts markdown → styled HTML
3. Purge + warm Cloudflare cache

### Default to custom tools now
Earlier, I'd use a microservice or SaaS tool for file conversion, scraping, monitoring. Now I just build it. FastAPI backend + React frontend + deploy via Coolify. Easier to maintain, no vendor dependencies, full control. 

Examples:
- Blog editor 
- API log monitor
- File converters
- Web scrapers

Total build time with AI coders: few hours to a day for simpler tools. More for complex ones. Maintenance: minimal.

---

## BACKUP STRATEGY  

### Hetzner 7-day backups enabled
Automatic snapshots, 7-day retention. Costs extra but worth it. 

Backups exist. Haven't needed them yet. Should test restoration process - haven't done it.

### No comprehensive backup strategy
Don't have mirror site or off-site backups. Everything critical is in Git repos. 

This is a gap. Should improve. Haven't prioritized it yet.

---

## COSTS  

### Hetzner VPS: €7.69/month
Includes 7-day backups. Hosting 30+ backends, n8n, Coolify. Still 30% capacity left.

### AWS: ~$5/month for shut-off instances
I maintain an EC2 instance and MySQL RDS for client testing. Both stay shut off unless needed (few times a month). 

Still get charged ~$5/month for baseline costs - IP allocation, DNS, other AWS oddities. Small price to keep testing environment ready.

### Vercel: Free tier
40+ React frontends. No cost.

### Neon PostgreSQL: Free tier (currently paid)
Was on free tier for months. Temporarily on paid for higher limits. Will likely drop back to free.

### Cloudflare: Free tier
DNS, caching, basic security. No cost.

### PostHog: Free tier
Web analytics. No cost.

### Brevo: Free tier
Email notifications. No cost.

### Claude Code Max: $100/month - biggest cost, worth every penny
This is like having a team of developers. Cloud engineering, Python, FastAPI, React - covered.

With CLI and API access, it now manages GitHub workflows, server operations, deployment scripts. I barely touch the command line manually anymore.

Worth it? Absolutely.  $20 keeps servers  running. $100 gives me a full dev team. The multiplier is massive.

---

## KEY TAKEAWAYS  

### Start simple, scale when needed
Don't jump to AWS/Azure until you actually need their scale. Smaller providers work fine for most use cases.

### Security is not optional
Fail2ban, SSH hardening, automatic updates. Set these up early. Defending attacks after the fact is painful.

### Self-host what you can, use SaaS where it's better
VPS + deployments = Hetzner + Coolify  
Frontends = Vercel  
Databases = Neon  
DNS/CDN = Cloudflare

Pick the right tool for each layer.

### Build custom tools when you need control
Blog automation, API monitoring, file processing - built custom. Took hours with AI coders, now fully under my control.

### Test your backups
I haven't. Should. Don't be me.

---

## RESOURCES  

**Hetzner + Coolify setup guide:**  
https://www.youtube.com/watch?v=taJlPG82Ucw

**Live apps (30+ micro-apps):**
Open Source - Hit 'Docs' for source codes
[tigzig.com](https://tigzig.com)
