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:
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:
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:
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:
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:
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:
/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:
[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:
sudo systemctl daemon-reload
sudo systemctl enable --now shellroute-task Verify its state and inspect recent output:
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:
shellroute status Follow new log output:
sudo journalctl -u shellroute-task -f Stop it cleanly:
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:
sudo systemctl daemon-reload
sudo systemctl enable --now shellroute-proxy@US Verify the service and inspect its startup JSON:
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:
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:
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:
sudo journalctl -u shellroute-proxy@US -f Stop the proxy cleanly:
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:
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:
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:
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.
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:
tmux new-session -d -s shellroute-proxy '/usr/local/bin/shellroute proxy --country US' List sessions or attach to view the output:
tmux list-sessions
tmux attach -t shellroute-proxy
Copy proxy_url from the startup JSON shown in tmux, then verify the expected country:
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:
/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:
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.