DEV Community

Vishvajeet Shukla
Vishvajeet Shukla

Posted on

How to Deploy a Node.js App on Hetzner Cloud (2026 Guide)

Hetzner Cloud is a favourite with developers who want a plain Linux server without a big cloud console in the way. You create a server, get an IP, and SSH in. Everything after that is up to you.

This guide walks through deploying a Node.js app (Express, NestJS, Fastify or similar) to a Hetzner Cloud server, from the first SSH login to HTTPS on your domain. At the end, we'll do the same deploy with ShipOwn to show which parts can be automated.

What you'll build

  • A hardened Ubuntu server on Hetzner Cloud
  • Your Node.js app running under PM2
  • Nginx proxying your domain to the app
  • A free Let's Encrypt certificate
  • A simple way to roll back

Step 1: Create the server

In the Hetzner Cloud console:

  1. Create a project, then Add Server.
  2. Pick a location close to your users.
  3. Choose Ubuntu (a current LTS release) as the image.
  4. Pick a small shared-vCPU plan to start. You can resize later.
  5. Add your SSH public key here. Don't rely on the root password email.
  6. Create a Firewall that allows inbound TCP 22, 80 and 443 only, and attach it.

Copy the server's public IPv4 address.

Step 2: First login and a deploy user

ssh root@SERVER_IP
apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Enter fullscreen mode Exit fullscreen mode

Now test ssh deploy@SERVER_IP in a new terminal before going further. Then disable password logins in /etc/ssh/sshd_config (PasswordAuthentication no) and restart SSH.

Optional but useful: install fail2ban so repeated failed logins get banned.

sudo apt install -y fail2ban
Enter fullscreen mode Exit fullscreen mode

Step 3: Install Node.js, Git and PM2

sudo apt install -y git nginx
# install Node.js LTS via NodeSource or nvm
sudo npm i -g pm2
Enter fullscreen mode Exit fullscreen mode

Step 4: Get your code onto the server

cd ~
git clone https://github.com/you/api.git
cd api
cp .env.example .env    # set PORT=3000 and secrets
npm ci
npm run build            # if you use TypeScript or NestJS
Enter fullscreen mode Exit fullscreen mode

Step 5: Start with PM2

pm2 start dist/main.js --name api    # or: pm2 start npm --name api -- start
pm2 save
pm2 startup                          # run the command it prints
Enter fullscreen mode Exit fullscreen mode

pm2 logs api shows output; pm2 status shows uptime and restarts.

Step 6: Nginx reverse proxy

Create /etc/nginx/sites-available/api:

server {
  listen 80;
  server_name api.example.com;
  client_max_body_size 10m;
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}
Enter fullscreen mode Exit fullscreen mode
sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

Always run nginx -t first. A typo in one file can stop Nginx serving every site on the server.

Step 7: DNS and HTTPS

Point an A record for api.example.com to the Hetzner IP. Once it resolves:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.example.com
Enter fullscreen mode Exit fullscreen mode

Step 8: Updates and rollback

The simplest update is git pull && npm ci && npm run build && pm2 reload api. The danger: if the build fails halfway, your running code and your files no longer match. A safer layout is releases/<timestamp> folders plus a current symlink that you switch only after a successful build.

The same deploy with ShipOwn

Steps 3 to 8 are the repetitive part. ShipOwn is a desktop app for Windows, macOS and Linux that does them over SSH from your machine. No ShipOwn agent is installed on your Hetzner server.

  1. Connect a server: paste the Hetzner IP, user deploy, and your key. The connection is tested before it's saved.
  2. Pick a source: your GitHub repo and branch, or a local folder.
  3. Review the plan: ShipOwn detects the framework (Express, NestJS, Fastify and others) and proposes install, build and start commands, port and domain. Edit anything, then approve.
  4. Watch it build: logs stream live from the server, or build on your PC and upload the output.
  5. Operate it: live URL, deployed commit, health, logs, redeploy, rollback.

What it handles for you:

  • PM2 process with port, health check and restart policy.
  • Nginx virtual host, tested before reload, and restored if Nginx rejects it.
  • Certbot certificate when you want HTTPS, with expiry shown in the app.
  • A release directory per deploy, so a failed deploy restores the previous release and you can roll back later.
  • fail2ban can be installed and viewed from the app.

You also get a server view with CPU, RAM, disk, load and PM2 processes, read straight from the server, plus an embedded terminal. Nothing extra is installed to collect these metrics.

Troubleshooting on Hetzner

  • Can't connect on port 80/443: check the Hetzner Cloud Firewall as well as ufw on the server. Both must allow the port.
  • 502 from Nginx: the app isn't listening on the port Nginx expects. Check pm2 logs.
  • EADDRINUSE: another process already uses the port. sudo ss -ltnp | grep 3000.
  • 413 Request Entity Too Large: raise client_max_body_size.

If you're stuck, ShipOwn's AI terminal (using your own Anthropic, OpenAI or DeepSeek key) proposes one command at a time with a reason, and you approve each one.

FAQ

Does ShipOwn work with Hetzner dedicated servers too? It targets any Linux server you can reach over SSH: a VPS, a cloud VM or bare metal.

Do I need Docker? No. Node apps run under PM2. Docker apps are supported if you prefer containers.

Conclusion

Hetzner gives you a clean, fast Linux box. Do the setup once by hand so you understand it, then use ShipOwn for repeat deploys, HTTPS and rollback. It has a free 30-day trial, no card required.


Written by Vishvajeet Shukla, full stack developer and creator of ShipOwn.

Top comments (0)