All posts
node.jssystemdlaunchdstartuplinuxmacosproduction

How to Run Node.js App on Server Startup (systemd, launchd, Oxmgr)

Make your Node.js app start automatically when the server boots. Complete guide covering systemd on Linux, launchd on macOS, and Oxmgr's built-in system service registration.

Oxmgr Team ·

Your Node.js app is running in production. You restart the server for a kernel update — and your app doesn’t come back up automatically. Users see errors until someone notices and SSHes in to restart it manually.

That’s a fixable problem. This guide covers all three ways to make a Node.js app start on server boot: systemd (Linux), launchd (macOS), and Oxmgr’s system service command.

The Problem

Running node server.js or oxmgr start in a terminal session ties the process to that session. When the terminal closes or the SSH connection drops, the process is killed. When the server reboots, nothing restarts automatically.

You need the OS init system to be responsible for starting your app.

Option 1: Oxmgr System Service (Recommended)

Oxmgr can register itself as a system service with one command:

sudo oxmgr service install

This creates the appropriate service file for your OS (systemd on Linux, launchd on macOS), enables it to start on boot, and starts it immediately.

To remove:

sudo oxmgr service uninstall

Check status:

oxmgr service status

The service uses your oxfile.toml to know what processes to manage. Oxmgr starts on boot, reads the config, and brings up all configured processes.

This is the fastest path. If you want to understand what’s happening underneath — or you’re using a different process manager — read on.

Option 2: systemd (Linux)

Systemd is the init system on Ubuntu, Debian, CentOS, RHEL, Fedora, Arch, and virtually every other modern Linux distribution.

Create the Service File

sudo nano /etc/systemd/system/myapp.service

Minimal working service:

[Unit]
Description=My Node.js Application
# Start after network is available
After=network.target

[Service]
Type=simple
User=nodeuser
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node /var/www/myapp/server.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Using Oxmgr via systemd

If you use Oxmgr for process management but want systemd to start it:

[Unit]
Description=Oxmgr Process Manager
After=network.target

[Service]
Type=forking
User=nodeuser
WorkingDirectory=/var/www/myapp
ExecStart=/usr/local/bin/oxmgr start --daemon
ExecStop=/usr/local/bin/oxmgr stop
ExecReload=/usr/local/bin/oxmgr reload
PIDFile=/var/run/oxmgr.pid
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and Start

# Reload systemd to see the new file
sudo systemctl daemon-reload

# Enable auto-start on boot
sudo systemctl enable myapp

# Start now
sudo systemctl start myapp

# Check status
sudo systemctl status myapp

Verify It Survives Reboot

sudo reboot
# Wait for server to come back
ssh user@your-server
systemctl status myapp
# Should show: active (running)

Useful systemd Commands

# View logs
journalctl -u myapp -f         # follow live
journalctl -u myapp -n 100     # last 100 lines
journalctl -u myapp --since "1 hour ago"

# Stop auto-start
sudo systemctl disable myapp

# Restart the service
sudo systemctl restart myapp

# Check if enabled on boot
systemctl is-enabled myapp

Production-Grade systemd Unit

A more complete service file with environment variables, security hardening, and resource limits:

[Unit]
Description=My Node.js Application
Documentation=https://github.com/your/repo
After=network.target postgresql.service redis.service
Requires=network.target

[Service]
Type=simple
User=nodeuser
Group=nodegroup
WorkingDirectory=/var/www/myapp

# The command to run
ExecStart=/usr/bin/node /var/www/myapp/dist/server.js

# Environment variables
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/etc/myapp/env  # load secrets from file

# Restart behavior
Restart=always
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5   # stop if it crashes 5 times in 60 seconds

# Graceful shutdown
KillSignal=SIGTERM
TimeoutStopSec=30

# Security (optional but recommended)
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=yes

# Resource limits
LimitNOFILE=65536   # max open file descriptors
MemoryMax=1G        # kill if memory exceeds 1GB
CPUQuota=200%       # max 2 CPU cores worth of CPU time

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

The EnvironmentFile approach keeps secrets out of the service file (which is world-readable):

# /etc/myapp/env — readable only by root
DATABASE_URL=postgresql://user:secretpassword@localhost/mydb
JWT_SECRET=your-jwt-secret-here
sudo chmod 600 /etc/myapp/env

For environment variable management best practices, see the Node.js environment variables guide.

Option 3: launchd (macOS)

macOS uses launchd for service management. This is relevant for macOS servers (Mac Minis as servers, Mac Pro rack units, or macOS development environments that should auto-start services).

Create a plist file:

nano ~/Library/LaunchAgents/com.myapp.server.plist

For user-level services (starts when user logs in):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.myapp.server</string>

    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/node</string>
        <string>/Users/myuser/myapp/server.js</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/Users/myuser/myapp</string>

    <key>EnvironmentVariables</key>
    <dict>
        <key>NODE_ENV</key>
        <string>production</string>
        <key>PORT</key>
        <string>3000</string>
    </dict>

    <!-- Start on load -->
    <key>RunAtLoad</key>
    <true/>

    <!-- Keep alive if it crashes -->
    <key>KeepAlive</key>
    <true/>

    <!-- Log files -->
    <key>StandardOutPath</key>
    <string>/Users/myuser/logs/myapp.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/myuser/logs/myapp-error.log</string>
</dict>
</plist>

Load and start:

launchctl load ~/Library/LaunchAgents/com.myapp.server.plist
launchctl start com.myapp.server

For system-level (starts on boot, before login) — use /Library/LaunchDaemons/ with sudo:

sudo cp com.myapp.server.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/com.myapp.server.plist

macOS launchctl commands:

launchctl list | grep myapp    # check if running
launchctl stop com.myapp.server
launchctl start com.myapp.server
launchctl unload ~/Library/LaunchAgents/com.myapp.server.plist  # disable

Creating a Dedicated User

Never run production Node.js as root. Create a dedicated system user:

# Linux
sudo useradd --system --no-create-home --shell /bin/false nodeuser

# Give it ownership of the app directory
sudo chown -R nodeuser:nodeuser /var/www/myapp
# In your systemd service
User=nodeuser
Group=nodeuser

If the app is compromised, the attacker has the permissions of nodeuser — not root.

Checking After Reboot

After any reboot, verify the process is running:

# systemd
systemctl status myapp

# Oxmgr
oxmgr status

# Check the port is listening
ss -tlnp | grep 3000
# or
lsof -i :3000

Common Issues

Service starts then immediately stops:

journalctl -u myapp --since "1 minute ago"
# Check the last exit code
systemctl status myapp | grep "Main PID"

Often a path issue — the node binary path or working directory is wrong.

Environment variables not loading: The service environment is not the same as your shell. Don’t assume ~/.bashrc values are available. Set everything explicitly in the service file or an EnvironmentFile.

Permissions error: The service user doesn’t have read access to the app directory or write access to log directories:

sudo chown -R nodeuser:nodeuser /var/www/myapp
sudo mkdir -p /var/log/myapp && sudo chown nodeuser /var/log/myapp

Port already in use after restart: The old process is still running (orphaned). Find and kill it:

lsof -ti:3000 | xargs kill
sudo systemctl start myapp

Summary

MethodPlatformBest For
oxmgr service installLinux + macOSFastest setup, uses oxfile.toml
systemd unit fileLinuxFull control, security hardening
launchd plistmacOSmacOS servers

For most setups, oxmgr service install gets you there in one command. For production Linux servers where you want fine-grained control over security and resource limits, write a proper systemd unit file.

See the PM2 vs systemd comparison if you’re deciding between process managers for your Linux setup.