Skip to main content

Custom SSH Provider

The Custom SSH provider lets you manage any server with SSH access, including on-premise servers, dedicated servers, or VMs on unsupported cloud providers.

How It Works

NightOps connects via SSH and executes your custom commands:

NightOps → SSH → Your Server → Execute Command

You define:

  • Start command: What runs when the schedule starts
  • Stop command: What runs when the schedule stops
  • Status command: How to check if the service is running

Use Cases

ScenarioStart CommandStop Command
Docker containersdocker start myappdocker stop myapp
Systemd servicessystemctl start myappsystemctl stop myapp
PM2 processespm2 start myapppm2 stop myapp
Custom scripts/opt/scripts/start.sh/opt/scripts/stop.sh
VM managementvirsh start myvmvirsh shutdown myvm

Setup

Step 1: Create SSH Key Pair

Generate a dedicated key pair for NightOps:

ssh-keygen -t ed25519 -C "nightops" -f ~/.ssh/nightops

This creates:

  • ~/.ssh/nightops — Private key (add to NightOps)
  • ~/.ssh/nightops.pub — Public key (add to server)

Step 2: Add Public Key to Server

On your target server:

# Create nightops user (recommended)
sudo useradd -m -s /bin/bash nightops

# Add public key
sudo mkdir -p /home/nightops/.ssh
sudo cat >> /home/nightops/.ssh/authorized_keys << 'EOF'
<paste contents of ~/.ssh/nightops.pub here>
EOF

sudo chown -R nightops:nightops /home/nightops/.ssh
sudo chmod 700 /home/nightops/.ssh
sudo chmod 600 /home/nightops/.ssh/authorized_keys

Step 3: Configure Sudo (if needed)

If your commands require root privileges:

# /etc/sudoers.d/nightops
nightops ALL=(ALL) NOPASSWD: /usr/bin/systemctl start myapp
nightops ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop myapp
nightops ALL=(ALL) NOPASSWD: /usr/bin/systemctl status myapp

Step 4: Add Provider in NightOps

  1. Go to Providers → Add Provider → Custom SSH
  2. Configure connection:
    • Host: Your server's hostname or IP
    • Port: SSH port (default: 22)
    • Username: nightops (or your user)
    • Private Key: Paste contents of ~/.ssh/nightops
  3. Configure commands:
    • Start Command: Command to start your service
    • Stop Command: Command to stop your service
    • Status Command: Command to check status (exit 0 = running)
  4. Click Test Connection
  5. Click Save Provider

Command Examples

Docker Compose

Start:  cd /app && docker compose up -d
Stop: cd /app && docker compose down
Status: cd /app && docker compose ps --quiet | grep -q .

Systemd Service

Start:  sudo systemctl start myapp.service
Stop: sudo systemctl stop myapp.service
Status: systemctl is-active --quiet myapp.service

Multiple Services

Start:  sudo systemctl start nginx && sudo systemctl start api
Stop: sudo systemctl stop api && sudo systemctl stop nginx
Status: systemctl is-active --quiet nginx && systemctl is-active --quiet api

Custom Script

# /opt/nightops/start.sh
#!/bin/bash
cd /app
source venv/bin/activate
python manage.py runserver &
echo $! > /var/run/myapp.pid
Start:  /opt/nightops/start.sh
Stop: kill $(cat /var/run/myapp.pid) && rm /var/run/myapp.pid
Status: test -f /var/run/myapp.pid && kill -0 $(cat /var/run/myapp.pid) 2>/dev/null

Status Command

The status command determines whether the service is running:

  • Exit code 0 → Running (shown as green in NightOps)
  • Non-zero exit → Stopped (shown as red in NightOps)

Examples

# Check if process exists
pgrep -f myapp

# Check systemd service
systemctl is-active --quiet myapp

# Check Docker container
docker inspect -f '{{.State.Running}}' mycontainer | grep -q true

# Check port is listening
nc -z localhost 8080

# Check HTTP endpoint
curl -sf http://localhost:8080/health > /dev/null

Security Best Practices

1. Dedicated User

Create a dedicated nightops user with minimal permissions:

sudo useradd -m -s /bin/bash nightops

2. Restricted SSH Key

Limit what the SSH key can do in authorized_keys:

command="/home/nightops/allowed-commands.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA...

3. Specific Sudo Rules

Only allow the exact commands needed:

nightops ALL=(ALL) NOPASSWD: /usr/bin/systemctl start myapp
nightops ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop myapp

4. Firewall

Restrict SSH access to NightOps IP ranges if possible.


Troubleshooting

"Connection Refused"

  1. Verify SSH is running on the target server
  2. Check the port is correct
  3. Verify firewall allows the connection

"Permission Denied"

  1. Verify the public key is in authorized_keys
  2. Check file permissions (700 for .ssh, 600 for authorized_keys)
  3. Verify the private key in NightOps is correct

"Command Failed"

  1. Test the command manually via SSH
  2. Check if sudo is required and configured
  3. Verify the command path is correct

Status Always Shows "Running" or "Stopped"

  1. Verify the status command exit code:
    ssh nightops@server 'your-status-command'; echo "Exit: $?"
  2. Exit 0 = running, non-zero = stopped

Limitations

  • No auto-discovery: Assets must be manually defined
  • No cost tracking: NightOps can't determine costs for custom resources
  • SSH access required: The server must be reachable via SSH

Next Steps