Run Shellroute on a headless server

Use systemd when a routed command or local proxy must survive logout. The service files below can restart the Shellroute process after an unsuccessful process exit. They do not repair a route while the process remains running. Use tmux for temporary work that you want to manage manually.

Choose a mode

Need Mode Behavior
Keep one long-running command on a country-specific route shellroute run Starts the route, gives the child command the proxy environment, and ends the route when the command stops. The command's HTTP client must honor those settings.
Keep a local HTTP proxy available for separately configured programs shellroute proxy Reports a local proxy_url in its startup JSON and listens until stopped. Only programs configured to use that URL are routed.
Keep a temporary command alive after disconnecting from SSH tmux Keeps the process attached to a terminal session. It does not start at boot or restart a failed process.

Proxy mode does not route the whole server. Other processes keep their normal connection unless you configure them to use the reported proxy_url.

Find absolute paths

systemd service files require absolute executable paths. Find them before creating a service:

terminal
command -v shellroute
command -v python3
command -v curl

The examples below use /usr/local/bin/shellroute, /usr/bin/python3, and /usr/bin/curl. Replace those paths, YOUR_USER, /home/YOUR_USER, and the example script path with values from your server.

Authenticate safely

Choose one authentication method. Never put an API key in a service file, command line, shell history, or repository.

Option 1: use a stored login

Log in once as the same operating-system user that will run the service:

terminal
sudo -iu YOUR_USER
shellroute login
# At the shellroute prompt
/exit
# At the operating-system shell
exit

Shellroute stores the login in ~/.shellroute/config.toml with mode 0600. Set User and HOME to that user in the service file, and omit the EnvironmentFile line shown below.

Option 2: use a protected environment file

Create a root-owned file that only root can read:

terminal
sudo install -d -m 700 -o root -g root /etc/shellroute
sudo install -m 600 -o root -g root /dev/null /etc/shellroute/env
sudoedit /etc/shellroute/env

Add this line in the editor, replacing the placeholder with your API key:

env file
SHELLROUTE_API_KEY=REPLACE_WITH_YOUR_API_KEY

You can obtain the key from an existing authenticated shell with shellroute reveal-key. Enter it only in the protected editor. Confirm the file permissions:

terminal
sudo chmod 600 /etc/shellroute/env
sudo chown root:root /etc/shellroute/env
sudo stat -c '%a %U %G' /etc/shellroute/env

The result should be 600 root root.

Run one long-running command with systemd

Use direct command mode when one verified-compatible program should use the route for its lifetime. First verify the country with the same Shellroute mode:

terminal
/usr/local/bin/shellroute run US -- /usr/bin/curl https://ipinfo.io/country

For country US, the expected response is US.

Create /etc/systemd/system/shellroute-task.service:

shellroute-task.service
[Unit]
Description=shellroute routed task
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=YOUR_USER
Environment=HOME=/home/YOUR_USER
EnvironmentFile=/etc/shellroute/env
ExecStart=/usr/local/bin/shellroute run US -- /usr/bin/python3 /opt/geo-check/monitor.py
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

If you used a stored login, remove EnvironmentFile=/etc/shellroute/env. Replace the user, home, executable, script, and country before starting the service.

Start the service and enable it at boot:

terminal
sudo systemctl daemon-reload
sudo systemctl enable --now shellroute-task

Verify its state and inspect recent output:

terminal
sudo systemctl status shellroute-task --no-pager
sudo journalctl -u shellroute-task -n 50 --no-pager

If the current shell uses the same stored login, confirm that the route appears in Shellroute:

terminal
shellroute status

Follow new log output:

terminal
sudo journalctl -u shellroute-task -f

Stop it cleanly:

terminal
sudo systemctl stop shellroute-task

systemd sends the stop signal to Shellroute. Shellroute forwards it to the child command, waits for the command to exit, and ends the route. If the child does not exit within five seconds, shellroute stops it.

With Restart=on-failure, an unsuccessful shellroute process exit starts a new shellroute session after ten seconds. The new session can have a different exit IP.

Run a persistent local proxy with systemd

Use proxy mode when another program is configured to use a local HTTP proxy. A template unit makes the country explicit in the service name. Create /etc/systemd/system/[email protected]:

[Unit]
Description=shellroute local proxy
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=YOUR_USER
Environment=HOME=/home/YOUR_USER
EnvironmentFile=/etc/shellroute/env
ExecStart=/usr/local/bin/shellroute proxy --country %i
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

If you used a stored login, remove EnvironmentFile=/etc/shellroute/env. Replace the user, home, and executable before starting the service.

Start a US proxy and enable it at boot:

terminal
sudo systemctl daemon-reload
sudo systemctl enable --now shellroute-proxy@US

Verify the service and inspect its startup JSON:

terminal
sudo systemctl status shellroute-proxy@US --no-pager
sudo journalctl -u shellroute-proxy@US -n 50 --no-pager

The startup JSON contains proxy_url. Its default is http://127.0.0.1:41900, but shellroute chooses another local port when that port is unavailable. Copy the reported value and use it everywhere below:

terminal
PROXY_URL='COPY_PROXY_URL_FROM_STARTUP_JSON'
curl --proxy "$PROXY_URL" https://ipinfo.io/country

For --country US, the expected response is US.

Configure a compatible program with the same URL. For programs that honor standard proxy environment variables:

terminal
export HTTP_PROXY="$PROXY_URL"
export HTTPS_PROXY="$PROXY_URL"
export http_proxy="$PROXY_URL"
export https_proxy="$PROXY_URL"

These variables affect only programs started from that environment, and only when their HTTP client honors the settings. They do not route the whole server.

Follow proxy logs:

terminal
sudo journalctl -u shellroute-proxy@US -f

Stop the proxy cleanly:

terminal
sudo systemctl stop shellroute-proxy@US

Only one persistent shellroute proxy instance can run at a time. Do not start another country instance before stopping the current one.

Switch countries

Stop and disable the current proxy instance before starting another country. To switch from US to DE:

terminal
sudo systemctl disable --now shellroute-proxy@US
sudo systemctl enable --now shellroute-proxy@DE

Read the new startup JSON, copy its proxy_url, and verify the expected country:

terminal
sudo systemctl status shellroute-proxy@DE --no-pager
sudo journalctl -u shellroute-proxy@DE -n 50 --no-pager
PROXY_URL='COPY_PROXY_URL_FROM_STARTUP_JSON'
curl --proxy "$PROXY_URL" https://ipinfo.io/country

For --country DE, the expected response is DE.

To change a direct-command service, stop it and edit its unit file. Change the country in ExecStart, then reload and start it:

terminal
sudo systemctl stop shellroute-task
sudoedit /etc/systemd/system/shellroute-task.service
sudo systemctl daemon-reload
sudo systemctl start shellroute-task

Only one proxy country instance can run at a time. A direct-command service is separate from the persistent proxy instance.

Handle a failed or depleted route

Restart=on-failure restarts the service only after an unsuccessful shellroute process exit. It does not restart a proxy process that remains running after its route fails or its balance is depleted.

When the route is unavailable, requests through the local proxy fail instead of using the server's direct connection. After resolving the cause, stop and start the service to open a new route:

Replace US with the country in the active service name.

terminal
sudo systemctl stop shellroute-proxy@US
sudo systemctl start shellroute-proxy@US
sudo journalctl -u shellroute-proxy@US -n 50 --no-pager

Copy the new proxy_url from the startup JSON and verify it again with https://ipinfo.io/country.

Use tmux for a temporary session

Use a stored login before starting Shellroute in tmux. Do not put the API key in the tmux command string.

Start a detached persistent proxy:

terminal
tmux new-session -d -s shellroute-proxy '/usr/local/bin/shellroute proxy --country US'

List sessions or attach to view the output:

terminal
tmux list-sessions
tmux attach -t shellroute-proxy

Copy proxy_url from the startup JSON shown in tmux, then verify the expected country:

terminal
PROXY_URL='COPY_PROXY_URL_FROM_STARTUP_JSON'
curl --proxy "$PROXY_URL" https://ipinfo.io/country

For --country US, the expected response is US.

From another terminal, stop the proxy cleanly as the same operating-system user:

terminal
/usr/local/bin/shellroute proxy stop

To switch countries, stop the current proxy and start a new tmux session with the new country. Do not run two proxy instances at once.

For one long-running command:

terminal
tmux new-session -d -s shellroute-task '/usr/local/bin/shellroute run DE -- /usr/bin/python3 /opt/geo-check/monitor.py'

To stop that command cleanly, attach with tmux attach -t shellroute-task and press Ctrl+C. Do not use tmux kill-session to stop shellroute.

tmux survives an SSH disconnect, but it does not start the process after a server restart or restart it after a failure. Use systemd for that.