- 1. Pick a provider
- 2. Path A — DigitalOcean droplet (recommended for first-timers)
- 3. Path B — AWS Lightsail (closest AWS equivalent)
- 4. Path C — AWS EC2 (full AWS, more knobs)
- 5. Path D — Hetzner / Vultr / Linode / generic VPS
- 6. First login & basic hardening
- 7. Install nginx and serve a test page
- 8. Point your domain at the server
- 9. Add HTTPS with Let's Encrypt
- 10. Hand it to Magic Deploy
- 11. When things go wrong
This guide assumes you can open a terminal and you own (or are about to buy) a domain name. No prior server admin experience required. Every command in here is copy-paste; every UI step names the button you're clicking. Total time on a clean coffee: ~30–45 minutes.
Why this exists. Magic Deploy's "Your own server" target wants an SSH-reachable Linux box and asks for the host, user, and SSH key. The scripts cookbook covers what to run on that box — but not how to get one in the first place. This page closes that gap.
1. Pick a provider
For a first server hosting a small web app, side project, or static site, optimize for: cheap, simple control panel, sane defaults, easy DNS. Performance differences at the bottom tier are tiny — the operational overhead matters far more.
| Provider | Cheapest plan | Why pick it | Skip if |
|---|---|---|---|
| DigitalOcean | $4/mo Droplet (512 MB) | Cleanest UI for first-timers. One-click Marketplace images (LAMP, MEAN, Docker, etc.). DNS hosting included free. | You're already paying for AWS / GCP and want one bill. |
| AWS Lightsail | $3.50/mo (512 MB) | Wraps EC2 in a DO-style flat-rate UI. Same datacenter as the rest of AWS — useful if you'll later add S3, RDS, etc. | You want autoscaling, VPCs, IAM-per-resource, or other "real" AWS features. Use EC2 instead. |
| AWS EC2 | ~$5/mo (t4g.nano) | The full AWS toolkit — VPC, security groups, IAM, autoscaling, AMIs. Required if you'll grow beyond one box. | You just want one box. Lightsail is the same thing with less paperwork. |
| Hetzner Cloud | ~€4/mo (CX22, 4 GB) | Best price/performance in Europe by a wide margin. Also has US-East / US-West. | You need a region near non-EU/US users. |
| Vultr / Linode (Akamai) | $5–6/mo (1 GB) | Wide region selection. Linode now has a proper DNS manager. Vultr has more locations. | No strong preference — DigitalOcean is friendlier. |
If you're stuck, pick DigitalOcean. Its UI labels match what tutorials say, its DNS panel is right next to its server panel, and its $200 / 60-day signup credit means your first server is effectively free. The walkthrough below uses DO; the Lightsail and EC2 sections cover the AWS deltas.
2. Path A — DigitalOcean droplet
2.1 Create an SSH key (one-time, on your Mac)
SSH keys replace passwords. You generate a keypair locally; the public half goes onto the server, the private half stays on your laptop. Anyone with the private half can log in, so don't share it.
# Skip if you already have ~/.ssh/id_ed25519
ssh-keygen -t ed25519 -C "[email protected]"
# Press Enter at every prompt (default path, empty passphrase) for the simplest setup.
# For better security, set a passphrase — macOS will remember it in Keychain.
# Print the public key so you can paste it into DigitalOcean:
cat ~/.ssh/id_ed25519.pub
The output starts with ssh-ed25519 AAAA…. That whole line is what you'll paste.
2.2 Create the droplet
- Sign up at digitalocean.com. They'll ask for a card; the $200 signup credit is applied automatically.
- Click Create → Droplets.
- Region: pick the one closest to your users. NYC3, SFO3, FRA1, SGP1 are popular.
- Image: Ubuntu 24.04 LTS x64. (LTS = "Long Term Support", patched until 2029.)
- Size: Basic → Regular → $4/mo (1 vCPU, 512 MB RAM, 10 GB SSD). Upgrade later if you need to; downgrading is harder.
- Authentication: SSH Key → New SSH Key. Paste the
ssh-ed25519 …line from step 2.1, give it a name like "MacBook," click Add SSH Key. Make sure the box for the key is checked. - Hostname: anything memorable, e.g.
web-prod-1. Cosmetic. - Create Droplet. ~30 seconds later you'll see a public IPv4 address. Copy it.
2.3 Test SSH
# Replace 203.0.113.42 with your droplet's IPv4 address
ssh [email protected]
First connection asks "Are you sure you want to continue connecting?" — type yes. You should land at a root@web-prod-1:~# prompt. Type exit for now — we'll come back after a security pass in section 6.
Permission denied (publickey)? Either you didn't tick the SSH key box during droplet creation, or your ~/.ssh/id_ed25519 isn't the same key whose public half you pasted. Run cat ~/.ssh/id_ed25519.pub and confirm it matches what's listed under Settings → Security → SSH Keys on DigitalOcean. If it doesn't, you can add the right key via the droplet's Recovery Console or the DO docs.
3. Path B — AWS Lightsail
Lightsail is AWS's answer to DigitalOcean: flat monthly pricing, simple UI, the same Linux underneath. If you're already in the AWS ecosystem (using S3, SES, Route 53, etc.), this lets you keep one bill.
3.1 Create the instance
- Sign in to lightsail.aws.amazon.com. (You need a regular AWS account; Lightsail just shares it.)
- Click Create instance.
- Region: pick the closest one. Note that this is the AWS region (e.g.
us-east-1) — useful later when wiring S3 or RDS in the same region. - Platform: Linux/Unix.
- Blueprint: OS Only → Ubuntu 24.04 LTS. (The "Apps + OS" blueprints are pre-installed stacks; we want a clean slate.)
- Instance plan: $3.50/mo (512 MB) for testing, $5/mo (1 GB) if your app uses Node / Rails / Django. The first 3 months on the $3.50/mo plan are free.
- SSH key pair: click Change SSH key pair → Upload new. Paste the contents of
~/.ssh/id_ed25519.pubfrom section 2.1. Don't use AWS's default key — the rest of this guide assumesid_ed25519. - Create instance. Wait until status flips from Pending to Running.
3.2 Static IP (important)
Lightsail instances get a public IP that changes if you stop/start them. Attach a static IP so DNS doesn't break:
- Click your instance → Networking tab → Create static IP.
- Attach it to the instance you just created. Static IPs are free while attached; they're billed only if you detach and leave them dangling.
- That static IP is the address you'll point your domain at.
3.3 Open ports for HTTP / HTTPS
Lightsail's firewall opens SSH (22) by default. To serve websites you need to open 80 and 443:
- Instance → Networking → IPv4 Firewall → Add rule.
- Add: HTTP (TCP, port 80) and HTTPS (TCP, port 443).
3.4 Test SSH
ssh ubuntu@<your-static-ip>
Lightsail Ubuntu instances use the ubuntu user (not root). Otherwise the rest of this guide is the same — when later sections say ssh root@…, use ssh ubuntu@… and prefix admin commands with sudo.
4. Path C — AWS EC2
EC2 is the full AWS compute primitive. More setup than Lightsail; necessary if you'll later want autoscaling groups, custom AMIs, multi-AZ, IAM roles per resource, or VPC peering.
4.1 Launch the instance
- EC2 Console → Launch instance.
- AMI: Ubuntu Server 24.04 LTS (free-tier eligible).
- Instance type:
t4g.nano(ARM, ~$3/mo) ort3.micro(x86, free tier for new accounts the first 12 months). - Key pair: Create new key pair if you don't have one in this region — type: ED25519, format: .pem. Save the
.pemto~/.ssh/aws-ec2.pemand runchmod 400 ~/.ssh/aws-ec2.pem(EC2 refuses to use world-readable keys). Or pick Use existing key pair if you uploaded one earlier. - Network settings: Allow SSH from My IP, plus Allow HTTP and Allow HTTPS from anywhere. (Allowing SSH from "anywhere" works but exposes port 22 to the world; you'll harden it later.)
- Storage: 8 GB gp3 is plenty for a starter app.
- Launch instance.
4.2 Elastic IP
EC2's default public IP also changes on stop/start. Allocate an Elastic IP and associate it with the instance — same reason as Lightsail's static IP. Elastic IPs are free while associated with a running instance; AWS charges if you let one sit unattached.
EC2 → Network & Security → Elastic IPs → Allocate, then Associate with your instance.
4.3 Test SSH
ssh -i ~/.ssh/aws-ec2.pem ubuntu@<elastic-ip>
Note the -i flag — EC2 doesn't use your default ~/.ssh/id_ed25519 unless you specifically uploaded that public key during launch. To stop typing -i every time, add this to ~/.ssh/config:
Host my-ec2
HostName <elastic-ip>
User ubuntu
IdentityFile ~/.ssh/aws-ec2.pem
Then ssh my-ec2 just works.
5. Path D — Hetzner / Vultr / Linode / generic VPS
The flow is identical to DigitalOcean, with different button labels. Across providers:
- Image: pick Ubuntu 24.04 LTS (or 22.04 LTS if 24.04 isn't offered).
- SSH key: paste your
id_ed25519.pubduring creation; this avoids the password-by-email path that some providers fall back to. - Firewall: open ports 22, 80, 443. Hetzner Cloud requires you to attach a Firewall resource separately (it's free); most others bake it into the create form.
- Static IP: VPS providers give you one by default that's stable across stop/start. Confirm before pointing DNS at it.
Concrete provider docs: Hetzner, Vultr, Linode. Once your instance is up and SSH works, jump to section 6.
6. First login & basic hardening
You now have an SSH-reachable box. Three things before installing anything: update packages, create a non-root user, turn on the firewall. ~5 minutes.
6.1 Update everything
ssh root@<your-ip> # or ssh ubuntu@<your-ip> on Lightsail/EC2
apt update && apt upgrade -y
apt install -y ufw fail2ban
ufw = uncomplicated firewall, friendlier wrapper around iptables. fail2ban auto-bans IPs that brute-force SSH.
6.2 Create a non-root user
Logging in as root every time is a footgun — one bad rm -rf wipes the box. Create a regular user and only escalate when needed.
adduser deploy # set a password when prompted
usermod -aG sudo deploy # give them sudo
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
Test from a second terminal (don't close the first one yet):
ssh deploy@<your-ip>
sudo whoami # should print "root"
If that works, you're done. From now on always SSH in as deploy.
6.3 Disable root SSH login (optional, recommended)
Edit /etc/ssh/sshd_config:
sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
Now: only key-based logins, only as deploy (or ubuntu on AWS). Brute-forcing SSH is now impossible without your private key.
6.4 Turn on the firewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable # answer 'y'
sudo ufw status # confirm all three rules are listed
UFW will not block your existing SSH session — but if you ever forget allow OpenSSH before enable, you can lock yourself out. Always allow SSH first.
7. Install nginx and serve a test page
sudo apt install -y nginx
sudo systemctl enable --now nginx
Open http://<your-ip> in a browser. You should see "Welcome to nginx!". If not: check sudo ufw status (port 80 allowed?) and on Lightsail/EC2 check the cloud-side firewall too.
7.1 Replace the default page
nginx serves /var/www/html by default. Drop a real index file in:
sudo bash -c 'cat > /var/www/html/index.html <<EOF
<!doctype html>
<title>hello</title>
<h1>It works.</h1>
<p>Served from $(hostname) at $(date).</p>
EOF'
# Make sure the deploy user can write here later (for rsync)
sudo chown -R deploy:deploy /var/www/html
Refresh the browser. You should see your new page.
8. Point your domain at the server
You need to tell the internet that yourdomain.com = your-server-ip. That's a DNS A record. Where you set it depends on which company hosts your DNS — usually either your registrar (Namecheap, Google Domains successor, Porkbun, GoDaddy) or a third-party host (Cloudflare, Route 53, DigitalOcean DNS).
Strong recommendation: put your DNS on Cloudflare. Free, fast, and adds a CDN + DDoS layer at no cost. The rest of this section assumes Cloudflare; the same A-record concept works at your registrar with different button labels.
8.1 Move DNS to Cloudflare (one-time)
- Sign up at cloudflare.com.
- Add a site → enter your domain → pick the Free plan.
- Cloudflare will scan and import your existing records. Skim them — anything weird? Usually just MX (mail) records and the existing A record. Click Continue.
- Cloudflare gives you two nameservers like
aria.ns.cloudflare.com,ben.ns.cloudflare.com. - Log in to your registrar (where you bought the domain). Find Nameservers for that domain and replace the existing ones with Cloudflare's.
- Wait. Propagation is usually ~10 minutes; can be up to 24 hours. Cloudflare will email when it's live.
8.2 Add the A record
Once Cloudflare is live for your domain:
- Cloudflare dashboard → your domain → DNS → Records.
- Add record:
- Type: A
- Name:
@(this means the apex, i.e.yourdomain.comitself). Use a subdomain name likeappif you wantapp.yourdomain.com. - IPv4 address: your server's IP from earlier.
- Proxy status: DNS only (gray cloud) for now. We'll switch this back to Proxied (orange cloud) after certbot runs — Let's Encrypt's HTTP-01 challenge can struggle through Cloudflare's proxy.
- TTL: Auto.
- Save. Add a second record for
wwwpointing to the same IP if you want bothyourdomain.comandwww.yourdomain.comto work.
8.3 Verify DNS
dig +short yourdomain.com
# should print your server's IP
curl -I http://yourdomain.com
# should return HTTP/1.1 200 OK from nginx
If dig shows nothing or the wrong IP, wait a few more minutes and try again. DNS caches can be sticky.
Other DNS hosts. Same idea, different UI: Route 53 → Hosted zones → your domain → Create record (Type A); DigitalOcean → Networking → Domains → A record; Namecheap → Domain List → Manage → Advanced DNS → Add new record (A Record). The data is always: type=A, host=@ (or subdomain), value=server-IP.
9. Add HTTPS with Let's Encrypt
Browsers shame plain HTTP. Let's Encrypt issues free certificates; certbot is the official client and auto-renews them every 60 days.
sudo apt install -y certbot python3-certbot-nginx
# Replace yourdomain.com with the actual hostname.
# Add -d www.yourdomain.com if you set up the www record too.
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will:
- Ask for an email (used for expiry warnings — give it a real one).
- Verify you control the domain by serving a temporary file at
http://yourdomain.com/.well-known/acme-challenge/…. (This is why the Cloudflare proxy needs to be off — DNS only mode — during this step.) - Edit your nginx config to add the cert + redirect HTTP → HTTPS.
- Reload nginx.
Visit https://yourdomain.com — there's the lock icon. Run sudo certbot renew --dry-run to confirm auto-renewal works.
9.1 Re-enable Cloudflare proxy (optional)
If you want Cloudflare's CDN + DDoS protection, switch the A record back to Proxied (orange cloud) now. In Cloudflare, set SSL/TLS → Overview to Full (strict) — this makes Cloudflare verify your origin's Let's Encrypt cert. Don't use Flexible; it's a redirect loop trap.
10. Hand it to Magic Deploy
You now have a server at a real domain serving real HTTPS. The last step is wiring it into LingCode's Magic Deploy popover so a deploy is one click.
- In LingCode, click the Magic Deploy button in the toolbar.
- Under Saved servers, click + Add server.
- Fill in:
- Host:
yourdomain.com(or the IP — both work) - User:
deploy(orubuntuon AWS) - Port: 22
- Auth: SSH key → pick
~/.ssh/id_ed25519(or your AWS.pem) - Remote directory:
/var/www/htmlfor a static site, or/srv/<app>for a Node/Python app — your choice.
- Host:
- Optional: paste before-launch and after-deploy scripts from the Scripts cookbook. For a static site: Before launch =
npm run build(if applicable); After deploy = empty (nginx serves the files directly). - Save. The new server appears in the popover. Tick its checkbox and click Deploy.
The popover shows progress: preflight → rsync → after-deploy. On success it prints the public URL. Subsequent deploys are one click.
11. When things go wrong
"Connection refused" on SSH
Either the server isn't running (check the provider dashboard), the firewall blocks port 22 (the cloud-side firewall on Lightsail/EC2 is separate from ufw — both must allow SSH), or you typed the wrong IP. Try nc -vz <ip> 22 from your laptop; if that fails, the firewall is the problem.
"Permission denied (publickey)"
Your local key isn't the one the server expects. ssh -v deploy@<ip> shows which keys are tried. If your key isn't on that list, run ssh-add ~/.ssh/id_ed25519. If the server doesn't have your public key, paste it manually via the provider's recovery console into ~deploy/.ssh/authorized_keys.
Domain doesn't resolve
dig +short yourdomain.com @1.1.1.1 queries Cloudflare's resolver directly, bypassing your local cache. If that shows the right IP but your browser doesn't, your laptop is caching — sudo dscacheutil -flushcache on Mac, then retry.
"403 Forbidden" or empty page after pointing DNS
nginx is serving but the document root is empty or has wrong permissions. Check ls -la /var/www/html. The owner should be deploy:deploy (or whatever user your deploy script writes as), and the files should be readable by www-data (group www-data on the directory works, or world-readable 0644 files inside a 0755 directory).
certbot fails: "DNS problem: NXDOMAIN" / "Failed authorization procedure"
Most common cause: Cloudflare proxy is on (orange cloud) and intercepts the HTTP-01 challenge. Switch to DNS only (gray cloud) and retry. Second most common: DNS hasn't fully propagated yet — dig from a few different resolvers and wait if results disagree.
certbot succeeds but browser still warns "Not secure"
Browser cache. Hard-refresh (⌘+Shift+R on Mac), or open in a private window. Check https://<yourdomain>/ with curl -vI — the cert chain should show "Let's Encrypt" as the issuer.
"This site can't be reached" but server is up
Provider-side firewall, again. Lightsail's IPv4 Firewall tab and EC2's Security Group are the most-forgotten settings. Both 80 and 443 must be open from 0.0.0.0/0 (anywhere) for public web traffic.
Have a server. Have a domain on it. Have HTTPS. Now drop in the right Before-launch + After-deploy scripts for your stack and you're done — every push to your repo is one Magic Deploy click away from production.
Related guides
- Magic Deploy script cookbook — what to run during Before-launch and After-deploy, by stack.
- Magic Deploy: web platforms — Vercel, Netlify, Railway, Fly.io, Heroku.
- Deploy hub — all four deploy targets at a glance.
- Glossary — every deploy term in plain English.