As Vercel falls from grace, hobbyists and others look for otions to circumvent it.
Here's a skeletal low-cost self-hosting option for everyone.
- Raspberry Pi 4
- NAT-capable modem/router connected to the internet
- One other device, PC, mobile, laptop, etc, connected to the same modem.
- Ubuntu 24.04 Server
- Raspberry Pi Imager v1.9.3
- nvm
- node
- vite
- nginx certbot python3-certbot-nginx
- tmux
- duckdns.org
- namecheap or some other domain host
- Burn Ubuntu 24.04 server on an SD card (64GB+) with the Raspberry Pi Imager. Set up SSH in the installer.
- Insert the SD card in the RaspPi4, connect it's ethernet cable to your modem/router.
- Set up static IP for your RaspPi4 device in the modem/router config (this is model-specific, for TELMEX watch https://youtu.be/NdHter6vAV8?si=RfPO2VGNQ7hTcx2Z)
- Forward ports 22, 80 and 443 with protocl TCP to your RaspPi in the modem config.
- Figure out your public ip from another PC connected to the same modem with
curl ifconfig.me - Set up a DDNS address with duckdns.org, using your public IP. E.g.
yoursubdom.duckdns.org - You may log into your rasppi remotely from an ssh client with
ssh username@yourpubliciporssh username@yoursubdom.duckdns.org, enter your password to login. - Install duckdns.org on your rasppi
crontab -e, follow https://www.duckdns.org/install.jsp - Once in, set up node and vite, and create a vite react project with something like
npx create-vite@latest my-react-app - Your
vite.config.tsfile should like like this:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0', // allow all network interfaces
port: 5173,
strictPort: true, // optional: fail if port is in use
cors: true, // optional: allow cross-origin
allowedHosts: [
'yourdomain.com',
'www.yourdomain.com',
'yoursubdom.duckdns.org'
]
}
})- Set up your new domain name on namecheap or any other domain name host. E.g.
yourdomain.com - Set up your namecheap/etc DNS records as. EDIT: This will break DNS for some VPNs, instead use step 14 in this gist.
CNAME Record @ yoursubdom.duckdns.org. Automatic
CNAME Record www youtsubdom.duckdns.org. Automatic
- Note that if you are already paying for namecheap, you can bypas duckdns.org altogether by adding this script to your crontab insetad of
duck.sh, follow https://www.namecheap.com/support/knowledgebase/article.aspx/29/11/how-to-dynamically-update-the-hosts-ip-with-an-https-request/ You may still keep yoursubdom.duckdns.org as a free option if you need it:
#!/bin/bash
# Update Namecheap DDNS for yourdomain.com
DOMAIN="yourdomain.com"
PASSWORD="102f110b52524a2aaf7cbae6f836be87" # from Namecheap's Advanced DNS tab
# Update root (@)
curl -s "https://dynamicdns.park-your-domain.com/update?host=@&domain=${DOMAIN}&password=${PASSWORD}" 2>&1 >> ~/namecheap/ddns.log
# Update www
curl -s "https://dynamicdns.park-your-domain.com/update?host=www&domain=${DOMAIN}&password=${PASSWORD}" 2>&1 >> ~/namecheap/ddns.log
# Update wildcard (*)
#curl -s "https://dynamicdns.park-your-domain.com/update?host=*&domain=${DOMAIN}&password=${PASSWORD}" >/dev/null- Configure nginx as such:
#!/bin/bash
sudo apt update
sudo apt install nginx -y
cat << EOF > /etc/nginx/sites-available/vite
server {
listen 80;
server_name yordomain.duckdns.org; # replace with your domain
location / {
proxy_pass http://127.0.0.1:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/vite /etc/nginx/sites-enabled/
sudo nginx -t # test config
sudo systemctl reload nginx
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.duckdns.org
cat << EOF > /etc/nginx/sites-available/yourdomain.com
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:5173; # or your app port
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_cache_bypass $http_upgrade;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com- Set up
sudo apt install tmuxand runtmux new-session -s myvite - Once in tmux run
npx vite --hostinside your vite project dir. - Detach from the tmux session with
Ctrl+band thend - You may now log out and the vite server should be running and displaying when you visit
www.mydomain.com - If you want to close vite reattach tmux with
tmux attach-session -t myviteand end it.
For extra security using ssh, set up a key by
- Running
ssh-keygen -t ed25519 -C "your_email@example.com"from your client machine - Then copy it to your RaspPi4 server with
ssh-copy-id username@remote_host - You can then test it by logging in with
ssh username@remote_host, it should not ask for password this time around. - Once in, disable password logins with
sudo nano /etc/ssh/sshd_configand settingPasswordAuthentication no