Deploy Deno Apps on a VPS
Install the secure TypeScript runtime, run a production HTTP server with systemd, and put Nginx + SSL in front — all on a $4/month VPS.
Prerequisites
- A RamNode VPS running Ubuntu 22.04 or 24.04 (any plan works; even the $4/month KVM SSD plan is sufficient)
- Root or sudo access over SSH
- A domain name pointed at your VPS IP (optional but recommended for SSL)
Update Your System
Start with a clean package state:
sudo apt update && sudo apt upgrade -yInstall Deno
Deno ships as a single binary. The official installer handles everything:
curl -fsSL https://deno.land/install.sh | shAdd it to your PATH:
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcVerify the installation:
deno --versionYou should see output like deno 2.x.x with v8 and TypeScript versions.
Create Your Application
Create a directory for your app and a simple HTTP server to test with:
mkdir -p /var/www/myapp
cd /var/www/myappCreate main.ts:
const port = parseInt(Deno.env.get("PORT") ?? "8000");
const handler = (_req: Request): Response => {
return new Response("Hello from Deno on RamNode!", {
status: 200,
headers: { "Content-Type": "text/plain" },
});
};
console.log(`Listening on http://localhost:${port}`);
Deno.serve({ port }, handler);Test it manually:
deno run --allow-net --allow-env main.tsIn a second terminal, verify the response:
curl http://localhost:8000You should see Hello from Deno on RamNode!. Hit Ctrl+C to stop.
Create a Dedicated System User
Running your app as root is a security risk. Create a low-privilege user instead:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin denoapp
sudo chown -R denoapp:denoapp /var/www/myappCopy the Deno binary to a system-wide location:
sudo cp ~/.deno/bin/deno /usr/local/bin/deno
sudo chmod +x /usr/local/bin/denoConfigure a systemd Service
Create a service file so your app starts automatically on boot and restarts on failure:
[Unit]
Description=Deno App
After=network.target
[Service]
Type=simple
User=denoapp
Group=denoapp
WorkingDirectory=/var/www/myapp
ExecStart=/usr/local/bin/deno run \
--allow-net \
--allow-env \
--allow-read=/var/www/myapp \
main.ts
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=PORT=8000
Environment=DENO_DIR=/var/www/myapp/.deno_cache
[Install]
WantedBy=multi-user.target- •
--allow-net,--allow-env,--allow-readfollow Deno's explicit permission model — only grant what your app needs. - •
DENO_DIRsets the module cache to a path the service user can write to. - •
Restart=on-failurewithRestartSec=5prevents rapid crash loops.
Create the cache directory and enable the service:
sudo mkdir -p /var/www/myapp/.deno_cache
sudo chown -R denoapp:denoapp /var/www/myapp/.deno_cache
sudo systemctl daemon-reload
sudo systemctl enable denoapp
sudo systemctl start denoappCheck status and follow logs:
sudo systemctl status denoapp
sudo journalctl -u denoapp -fInstall and Configure Nginx
Putting Nginx in front gives you SSL termination, static file serving, gzip compression, and rate limiting without changing your app code.
sudo apt install -y nginxCreate a site config:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}Enable the site and test:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxAdd SSL with Let's Encrypt
sudo apt install -y certbot python3-certbot-nginxRequest a certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comVerify auto-renewal:
sudo certbot renew --dry-runOpen Firewall Ports
If you are using UFW:
sudo ufw allow 'Nginx Full'
sudo ufw enableThis allows ports 80 and 443. Your Deno app on port 8000 stays internal and is not exposed directly.
Pre-cache Dependencies (Optional)
On first startup, Deno fetches and caches remote imports. To avoid this delay at runtime, pre-cache during deployment:
sudo -u denoapp DENO_DIR=/var/www/myapp/.deno_cache deno cache main.tsFor apps using an import map or lock file:
sudo -u denoapp DENO_DIR=/var/www/myapp/.deno_cache deno cache --import-map=import_map.json main.tsDeploying Updates
For a simple update workflow without a full CI pipeline:
cd /var/www/myapp
git pull origin main
sudo -u denoapp DENO_DIR=/var/www/myapp/.deno_cache deno cache main.ts
sudo systemctl restart denoapp
sudo systemctl status denoappTroubleshooting
Service fails to start
Check the logs first:
sudo journalctl -u denoapp -n 50 --no-pagerCommon causes: missing permission flags, wrong WorkingDirectory, or a port already in use.
Permission denied errors
Deno's sandbox throws permission errors if your app accesses something not covered by the flags in your service file. Add the required flag (--allow-read, --allow-write, etc.) and restart.
Module download failures on first run
The denoapp user needs write access to DENO_DIR. Double-check ownership:
sudo chown -R denoapp:denoapp /var/www/myapp/.deno_cache502 Bad Gateway from Nginx
This usually means your Deno process is not listening on the expected port. Confirm with:
sudo ss -tlnp | grep 8000Next Steps
With your Deno app running behind Nginx on a RamNode VPS you have a production-ready setup. From here you might consider:
- Adding a GitHub Actions workflow to automate deployments via SSH on push
- Using Deno's built-in
Deno.servewith WebSockets for real-time features - Mounting a RamNode block storage volume for persistent file uploads
- Running multiple Deno apps on the same VPS using different ports and Nginx
server_nameblocks - Adding monitoring with something lightweight like Netdata or a simple uptime check
