How to Deploy a Nuxt App to a VPS with GitHub Actions and Systemd

You do not need a complicated platform to deploy a Nuxt app. A VPS, GitHub Actions, and systemd are enough for a clean production setup.
The idea is simple:
- Push code to GitHub
- GitHub Actions installs dependencies and builds the app
- The build output is copied to your VPS
- systemd restarts the Nuxt server
- Nginx sits in front as a reverse proxy
This setup is boring in the best way.
1. Build the Nuxt App
Most Nuxt apps produce a .output directory after build:
npm run build
Inside .output/server/index.mjs is the server entry point that can be run with Node.js.
On the VPS, the app can start like this:
node .output/server/index.mjs
2. Create a systemd Service
Create a service file like:
[Unit]
Description=Nuxt App
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/www/my-nuxt-app
ExecStart=/usr/bin/node .output/server/index.mjs
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=NITRO_HOST=127.0.0.1
[Install]
WantedBy=multi-user.target
Then enable it:
sudo systemctl daemon-reload
sudo systemctl enable my-nuxt-app
sudo systemctl start my-nuxt-app
3. Put Nginx in Front
Your Nuxt server should usually listen on localhost. Nginx handles public HTTP/HTTPS traffic.
Example:
server {
server_name example.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;
}
}
Add Let's Encrypt after that and you have HTTPS.
4. Add GitHub Actions
A simplified workflow looks like this:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- name: Copy build to VPS
run: echo "Use rsync or scp here"
- name: Restart service
run: echo "SSH into VPS and restart systemd"
In a real workflow, store SSH keys, host, user, and target path as GitHub Secrets.
5. Restart Safely
After copying files, restart your service:
sudo systemctl restart my-nuxt-app
sudo systemctl status my-nuxt-app
Check logs if something breaks:
journalctl -u my-nuxt-app -f
This is one reason systemd is useful: it gives you process management, auto-restart, logs, and boot startup.
Why This Setup Works
This deployment pattern is simple but powerful:
- GitHub builds the app consistently
- The VPS only runs production output
- systemd keeps the process alive
- Nginx handles domains and HTTPS
- No dashboard lock-in
- No surprise platform pricing
For personal blogs, portfolios, tools, and small SaaS apps, it is more than enough.
Final Thoughts
Modern deployment does not have to be complicated.
If you understand GitHub Actions, SSH, systemd, and Nginx, you can deploy almost anything. More importantly, you understand what is actually happening when your app goes live.
That knowledge is worth keeping.
