VPS for Freelancers: How to Run Your Entire Business on One Server
Freelancers and independent developers pay for a fragmented collection of SaaS tools — Notion for notes, Trello for projects, Basecamp for clients, Calendly for bookings, Mailchimp for newsletters, Squarespace for the portfolio, Dropbox for files. Add it up and you’re paying $200–500/month for tools that share your data with a dozen companies and lock you into their platforms.
A $20–50/month VPS can replace most or all of this. This guide shows exactly how to consolidate your freelance business onto a single self-hosted server — client sites, project management, invoicing, file storage, email, and more.
The Freelancer SaaS Stack (and What It Costs)
| Tool Category | Common SaaS | Monthly Cost | Self-Hosted Alternative |
|---|---|---|---|
| Portfolio/website | Squarespace, Webflow | $16–36 | Hugo/Jekyll on Nginx |
| Client sites (5) | Shared hosting × 5 | $25–75 | Nginx virtual hosts |
| Project management | Basecamp, Asana | $15–30 | Plane, Taiga |
| Invoicing | FreshBooks, Wave | $0–30 | InvoiceNinja |
| File storage | Dropbox, Google Drive | $10–20 | Nextcloud |
| Git repositories | GitHub private | $4–20 | Gitea |
| Password manager | 1Password, Bitwarden | $3–5 | Vaultwarden |
| Uptime monitoring | Pingdom | $15–50 | UptimeKuma |
| Total | $88–266/mo | $20–50/mo (VPS) |
💡 VPS.DO Tip: A 4 vCPU / 8 GB RAM VPS comfortably runs all of these simultaneously. VPS.DO’s USA VPS plans start at $20/month with 500 GB SSD. View Plans →
The Freelancer VPS Stack
Core infrastructure
- Ubuntu 22.04 LTS (OS)
- Nginx (web server and reverse proxy)
- Docker + Docker Compose (application isolation)
- Let’s Encrypt / Certbot (free SSL for all domains)
- MariaDB or PostgreSQL (database)
- Redis (caching and sessions)
Business applications (all self-hosted)
- Nextcloud — File storage, contacts, calendar, document editing
- InvoiceNinja — Professional invoicing, client portal, time tracking
- Plane or Taiga — Project and task management
- Gitea — Private Git repositories for client code
- Vaultwarden — Password manager (Bitwarden-compatible)
- Uptime Kuma — Monitor all client sites from one dashboard
Step 1: Initial VPS Setup
sudo apt update && sudo apt upgrade -y
sudo apt install nginx docker.io docker-compose certbot python3-certbot-nginx -y
sudo systemctl enable nginx docker
sudo usermod -aG docker $USER
newgrp docker
# Create directory structure
sudo mkdir -p /var/www/portfolio
sudo mkdir -p /var/apps/{nextcloud,invoice,gitea,kuma,vaultwarden}
sudo chown -R $USER:$USER /var/apps
Step 2: Deploy Nextcloud (File Storage + Collaboration)
nano /var/apps/nextcloud/docker-compose.yml
version: '3.8'
services:
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: unless-stopped
volumes:
- nextcloud_data:/var/www/html
- /var/apps/nextcloud/data:/var/www/html/data
environment:
- MYSQL_HOST=db
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=SecurePass123!
- NEXTCLOUD_TRUSTED_DOMAINS=cloud.yourdomain.com
networks:
- proxy-network
db:
image: mariadb:10.11
container_name: nextcloud-db
restart: unless-stopped
volumes:
- nextcloud_db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=RootPass123!
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=SecurePass123!
networks:
- proxy-network
volumes:
nextcloud_data:
nextcloud_db:
networks:
proxy-network:
external: true
docker network create proxy-network 2>/dev/null || true
cd /var/apps/nextcloud && docker compose up -d
Nginx config for Nextcloud
sudo nano /etc/nginx/sites-available/cloud.yourdomain.com
server {
listen 80;
server_name cloud.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name cloud.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem;
client_max_body_size 10G;
proxy_read_timeout 300s;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
location / {
proxy_pass http://nextcloud:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Step 3: Deploy InvoiceNinja (Invoicing)
nano /var/apps/invoice/docker-compose.yml
version: '3.8'
services:
invoiceninja:
image: invoiceninja/invoiceninja:latest
container_name: invoiceninja
restart: unless-stopped
volumes:
- ./storage:/var/www/app/storage
- ./public:/var/www/app/public
environment:
- APP_URL=https://invoice.yourdomain.com
- DB_HOST=invoice-db
- DB_DATABASE=invoiceninja
- DB_USERNAME=ninja
- DB_PASSWORD=NinjaPass123!
- APP_KEY=base64:YOUR_32_CHAR_KEY_HERE
networks:
- proxy-network
invoice-db:
image: mariadb:10.11
container_name: invoice-db
restart: unless-stopped
volumes:
- invoice_db:/var/lib/mysql
environment:
- MYSQL_DATABASE=invoiceninja
- MYSQL_USER=ninja
- MYSQL_PASSWORD=NinjaPass123!
- MYSQL_ROOT_PASSWORD=RootPass123!
networks:
- proxy-network
volumes:
invoice_db:
networks:
proxy-network:
external: true
cd /var/apps/invoice && docker compose up -d
Step 4: Deploy Uptime Kuma (Client Site Monitoring)
docker run -d \
--name uptime-kuma \
--network proxy-network \
--restart unless-stopped \
-v uptime-kuma:/app/data \
louislam/uptime-kuma:latest
Add each client site as a monitor in the Uptime Kuma dashboard. You’ll receive instant email/Telegram/Slack notifications when any client site goes down — before the client notices.
Step 5: Deploy Vaultwarden (Password Manager)
docker run -d \
--name vaultwarden \
--network proxy-network \
--restart unless-stopped \
-v vaultwarden_data:/data \
-e SIGNUPS_ALLOWED=false \
-e ADMIN_TOKEN=your-secure-admin-token \
vaultwarden/server:latest
Access at https://vault.yourdomain.com. Disable public signups and use it as your personal password vault — compatible with the official Bitwarden browser extension and mobile apps.
Step 6: Portfolio and Client Sites with Nginx
# Your portfolio (Hugo static site)
sudo mkdir -p /var/www/yourdomain.com
rsync -avz ./public/ /var/www/yourdomain.com/
# Client sites (WordPress via LEMP)
sudo mkdir -p /var/www/client1.com
# ... standard WordPress setup per site
Step 7: Automated Backups for Everything
nano ~/backup-freelancer.sh
#!/bin/bash
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/var/backups/freelancer/$DATE"
mkdir -p $BACKUP_DIR
# Backup all Docker volumes
docker run --rm \
-v uptime-kuma:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/uptime-kuma.tar.gz -C /data .
docker run --rm \
-v vaultwarden_data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/vaultwarden.tar.gz -C /data .
# Backup all databases
docker exec nextcloud-db mysqldump -u root -pRootPass123! --all-databases \
| gzip > $BACKUP_DIR/databases.sql.gz
# Backup client website files
tar czf $BACKUP_DIR/websites.tar.gz /var/www/
# Sync to a second VPS (optional)
rsync -avz $BACKUP_DIR/ backup@BACKUP_VPS_IP:/backups/
# Remove backups older than 30 days
find /var/backups/freelancer -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
echo "Backup complete: $DATE"
chmod +x ~/backup-freelancer.sh
crontab -e
# 0 2 * * * /bin/bash /root/backup-freelancer.sh
The Freelancer VPS Monthly Cost Breakdown
| Component | Self-hosted cost | SaaS equivalent |
|---|---|---|
| VPS (4 GB RAM, 500 GB SSD) | $20/month | $88–266/month |
| Domain name | $10–15/year | Usually included above |
| SSL certificates | $0 (Let’s Encrypt) | $0–100/year |
| Backup storage | $0–5/month | Often extra |
| Total | ~$21–26/month | $88–300/month |
Annual savings: $800–3,300/year — for a freelancer, that’s a meaningful amount.
Time Investment: Is It Worth It?
The honest answer: self-hosting requires an upfront time investment (5–10 hours to set everything up) and ongoing maintenance (1–2 hours/month for updates, monitoring, and backups). If your time is worth $100+/hour, crunch the numbers carefully.
The sweet spot: if you’re a developer or technical freelancer who already knows Linux basics, self-hosting your business tools is a Saturday afternoon project that saves hundreds of dollars per year and gives you skills directly applicable to client work.