A server.
Your laptop is not a server. Close it, lose wifi, and the app is gone. Rent a small computer that lives in a datacenter and never sleeps. $5–$10 a month gets you more than enough for a side project.
You'll pick a provider, click "create", and walk away with an
IP address and an SSH key login. From there, everything happens
over ssh from your laptop.
Cheap and simple
Hetzner Cloud, DigitalOcean, Vultr, Linode. Click a button, pay a few dollars a month, get a fresh Ubuntu box.
If you're already on a cloud
AWS Lightsail, GCP Compute Engine, Azure VM. Same shape, plumbed into the rest of your account.
# first connection, as root
ssh root@203.0.113.42
# patch the box, make a non-root user, turn on the firewall
apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy
ufw allow OpenSSH
ufw enable
An empty box is not an app. You can SSH in. The box is online. Now you need your code on it.
Your app.
Get your code onto the box. The simplest path is
git clone. Install the runtime, install
dependencies, point at whatever it needs (env vars, a SQLite
file, a connection string), and bind it to a port. 3000 is
fine for now.
Open http://<your-ip>:3000 in your browser.
If you see your app, it works. This is the moment your
software is, technically, on the internet.
Getting code on the box
git clone from a public repo, or scp / rsync for private code. Later: a GitHub Actions deploy step.
Picking a port
Pick anything above 1024 that isn't taken. 3000 (Node), 8000 (Python), 8080 (Go) are conventional. You'll close the public-facing one in Phase 4.
# install the runtime and git sudo apt install -y nodejs npm git # pull your code, install deps, run it git clone https://github.com/you/your-app.git cd your-app npm ci npm run build PORT=3000 npm start
# let port 3000 through the firewall (temporarily)
sudo ufw allow 3000
Close the SSH session, the app dies. Reboot the box, nothing comes back up. Foreground processes don't survive anything. You need a process manager.
A process manager.
You need something between you and your app that keeps it alive: across crashes, across reboots, across deploys. systemd is already on every Ubuntu box. It's free, native, and good enough for almost every side project.
Write a tiny unit file, enable it, and forget about it. Logs
go to journalctl, which is searchable, rotates
for you, and survives reboots.
Default (Linux)
systemd. Already installed. No new dependencies. Same shape on every Linux box you'll ever rent.
Node-friendly
PM2. Clustering, log rotation, zero-downtime reloads. Nice if you want batteries included.
Python-friendly
Supervisor, or just write a systemd unit that runs uvicorn/gunicorn.
[Unit] Description=My app After=network.target [Service] Type=simple User=deploy WorkingDirectory=/home/deploy/your-app ExecStart=/usr/bin/node /home/deploy/your-app/server.js Environment=PORT=3000 NODE_ENV=production Restart=always RestartSec=3 [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
# follow logs in real time
journalctl -u myapp -f
Your app is alive, but it's bare HTTP on a weird port. No HTTPS, no real URL, anyone snooping the wifi at a coffee shop can read every login. Time for a reverse proxy.
Keep reading.
Drop your email to unlock the last two pieces.
A reverse proxy.
A reverse proxy is a tiny piece of software that sits between
the internet and your app. It listens on the standard web
ports (80 and 443), terminates TLS,
and forwards the request to your app on
localhost:3000.
Your app stops being on the public internet directly. It only listens on localhost. The proxy is the front door, and it's good at being a front door: it gzips responses, serves static files fast, and gives you one clean place to add rate limits, headers, and routing rules later.
The classic
Nginx. Boring in the best way. Every system admin has read your config before. Tons of documentation.
Newer, friendlier
Caddy. Smaller config, and it gets the HTTPS certificate for you automatically — no certbot step in Phase 5.
Container-native
Traefik. Best if you're running Docker or Kubernetes. Auto-discovers services from labels.
sudo apt install -y nginx
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}
# turn the site on sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx # open 80/443, close the direct app port sudo ufw allow 'Nginx Full' sudo ufw delete allow 3000
// before: app.listen(3000)
app.listen(3000, '127.0.0.1');
People still type an IP address. Browsers still scream "Not Secure" because there's no certificate. One last piece.
Keep reading.
Drop your email to unlock the last piece.
A domain name.
An IP address is not a name people want to type. Buy a domain ($10–15 a year), and add an A record that points it at your server's IP. DNS propagation usually takes minutes, sometimes an hour.
Then get a free TLS certificate from Let's Encrypt. Certbot does the whole dance: it requests the cert, edits your nginx config to serve HTTPS, and sets up a cron job to renew it every 60 days. Visit your domain. Green lock. Done.
That's it. Your app is on the internet, on a name you own, over HTTPS, surviving reboots and crashes.
Registrars worth using
Cloudflare Registrar (at-cost, no markup). Porkbun. Namecheap. Avoid GoDaddy.
DNS hosting
Cloudflare DNS, free and fast. Some registrars host DNS for you too.
Certificates
Let's Encrypt via certbot, or Caddy (if you picked Caddy in Phase 4, you already have HTTPS).
# add two records
yourdomain.com A 203.0.113.42
www CNAME yourdomain.com
# install certbot + the nginx plugin sudo apt install -y certbot python3-certbot-nginx # certbot reads your nginx config, gets a cert, rewrites the # config to serve https, and installs a renewal cron sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# open it. green lock. real URL.
https://yourdomain.com
One box is one fuse. When the box dies, your site goes down. When you outgrow it, head over to System Evolution to see exactly what to scale next, and in what order.