osw-studio / docs /VPS_DEPLOYMENT.md
otst's picture
server mode workspace multitenancy with per-workspace SQLite isolation, user accounts and admin management
be3d128

VPS Deployment Guide

Deploy OSW Studio on a VPS with full security hardening. Tested on Hetzner Cloud, but applies to any Ubuntu/Debian VPS.


Pre-Creation (Hetzner Console)

Skip this section if using a different VPS provider. The server setup steps below work on any Ubuntu 22.04+ server.

1. Add SSH Key

Security β†’ SSH Keys β†’ Add SSH Key

If you don't have one locally, generate it first:

ssh-keygen -t ed25519 -C "osws-server"
cat ~/.ssh/id_ed25519.pub

Paste the public key into Hetzner.

2. Create Cloud Firewall

Firewalls β†’ Create Firewall

Inbound rules (TCP only):

Port Protocol Source
22 TCP Any
80 TCP Any
443 TCP Any

Do NOT add port 3000 or any other app ports.

3. Create Server

  • Select your SSH key
  • Attach the firewall
  • Smallest instance (CX11/CX21) is fine for testing

Server Setup

SSH into the server:

ssh root@<your-ip>

Update System

apt update && apt upgrade -y

Create Non-Root User

adduser osws --disabled-password --gecos ""
mkdir -p /home/osws/.ssh
cp ~/.ssh/authorized_keys /home/osws/.ssh/
chown -R osws:osws /home/osws/.ssh
chmod 700 /home/osws/.ssh
chmod 600 /home/osws/.ssh/authorized_keys

Harden SSH

sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart ssh

Install fail2ban

apt install fail2ban -y
systemctl enable fail2ban --now

Add Swap (Prevents OOM on Small Instances)

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Install & Configure Nginx

apt install nginx -y

cat > /etc/nginx/sites-available/osws << 'EOF'
server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
    }
}
EOF

ln -s /etc/nginx/sites-available/osws /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

App Deployment

Switch to osws User

su - osws

Install Node.js

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20

Clone Repository

git clone https://github.com/o-stahl/osw-studio.git
cd osw-studio

Create Environment File

SESSION_SECRET=$(openssl rand -base64 32)
ANALYTICS_SECRET=$(openssl rand -base64 32)
SECRETS_ENCRYPTION_KEY=$(openssl rand -base64 32)

cat > .env << EOF
NEXT_PUBLIC_SERVER_MODE=true
SESSION_SECRET=$SESSION_SECRET
SECURE_COOKIES=false
ANALYTICS_SECRET=$ANALYTICS_SECRET
SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY
EOF

Note: SECURE_COOKIES=false is required when running HTTP without SSL. Remove this line after adding HTTPS.

No ADMIN_PASSWORD needed. On first visit to /admin, you'll create the admin account interactively.

Build & Start

npm install
npm run build
npm install -g pm2
pm2 start npm --name "osws" -- start
pm2 save

Enable PM2 on Boot

Exit back to root (exit) and run:

env PATH=$PATH:/home/osws/.nvm/versions/node/v20.18.1/bin pm2 startup systemd -u osws --hp /home/osws

Note: Adjust the Node version path if you installed a different version.


Access

  • Studio: http://<your-ip>/
  • Admin: http://<your-ip>/admin

Updating OSW Studio

su - osws
cd ~/osw-studio
git pull
npm install
npm run build
pm2 restart osws

Important: Any changes to NEXT_PUBLIC_* environment variables require a rebuild (npm run build) β€” these are baked in at compile time.


Adding HTTPS (Recommended)

Once you have a domain pointing to your server:

As root:

apt install certbot python3-certbot-nginx -y
certbot --nginx -d your-domain.com

Then update the environment and rebuild:

su - osws
cd ~/osw-studio

# Edit .env to remove SECURE_COOKIES=false
nano .env

npm run build
pm2 restart osws

Security Checklist

  • βœ… Never expose port 3000 directly β€” always use nginx as reverse proxy
  • βœ… App runs as non-root user osws
  • βœ… SSH is key-only, no password authentication
  • βœ… Cloud firewall blocks all ports except 22, 80, 443
  • βœ… fail2ban protects against brute force attempts
  • βœ… Swap prevents out-of-memory crashes on small instances

Next Steps