If you already use Amp and you keep WordPress sites on a DigitalOcean Droplet or a Hetzner Cloud server that isn’t in git, you don’t have to open another SSH session every time a theme, plugin or typo needs a fix. You can run Amp on the VPS itself, as a runner, and start those threads from ampcode.com or the Amp apps on your other devices.
A runner is not a new kind of cloud computer. Amp’s docs put it simply: you start amp --no-tui on a machine you already have, and Amp creates threads there with the directories, tools and logins that are on that machine. Amp does not create, size, pause or wake the VPS. The runner is available while the process runs and the machine is on, and every thread you start on it shares that one machine.
That is the right shape for a live WordPress stack you already pay for. The files are on the server, the PHP and database tools are there, and you already know how the site is hosted. Installing a runner does not move the sites, change their DNS, introduce git or create a deployment process. Edits to live files or the database are still production changes. SSH remains the way in for the first setup, and for recovery if Amp is unavailable. You don’t need a second VPS just to follow this guide; if the sites already span several servers, put a runner on each one and give each a different ID. I later ran this end to end on a Laravel Forge Ubuntu 24.04 box that already hosted three WordPress sites.
“I thought you guys were all about non-local dev, so why do you have the runners?” @thorstenball on runners, which run Amp agents on machines you own: a Raspberry Pi at home, a lab of test hardware, GPU boxes, a macOS build machine. “I think runners on different machines are non-local too, you know?”
— Amp (@AmpCode) October 2, 2026
How a runner differs from an orb, a box or a satellite
Amp now has several ways to give an agent a machine, and they are easy to mix up. These are the distinctions in the current docs:
| Kind | What it is | You would use it when |
|---|---|---|
| Orb | A machine Amp creates for a thread, then pauses when you are done. Amp sizes, updates and maintains it. | You want a fresh machine for a task, and you don’t need hardware or files that exist only on a server you already have. |
| Runner | The Amp CLI, started with amp --no-tui, on a machine you provide. Threads run with that machine’s directories, tools and logins. | The work has to happen on a specific computer, such as a WordPress VPS, a Mac Mini, a GPU box or a lab machine. |
| Box | An always-on virtual machine Amp hosts for apps you deploy from orbs. Each box includes its own runner. There is no public IP, so you don’t SSH in. | You want Amp to host the running app as well as the agent. This is covered separately. |
| macOS Satellite | A Mac in the cloud that an orb can start when a task needs Xcode or a simulator. It is in beta, behind a waitlist, and it is not a VPS runner. | You are building an iOS or Mac app in an orb and need a real Mac next to it. |
A Linux cloud instance next to your sites is a runner, not a satellite. If someone offers you “satellite-style” control of a VPS, what Amp actually documents is this: you sign in to the same Amp account on the VPS, start a runner, and pick that runner in the location picker when you create a thread.
What you’ll need
The Amp CLI supports macOS, Linux, and Windows through WSL. For a typical DigitalOcean or Hetzner WordPress host, that means Ubuntu or Debian, systemd as PID 1, and a dedicated non-root account that can already read and write the site files. Threads on a runner run as the operating-system user that started the process, and they can read and change that user’s files. Don’t start the runner as root, and don’t recursively change a working site’s ownership just to make Amp fit. A non-root account with unrestricted passwordless sudo can still confer that power, so keep administrative setup, such as installing packages or enabling linger, separate from the identity the agent runs as. WordPress’s own file-permissions guidance is that ownership varies between hosts; the right account is the one that already maintains the sites, not an assumed identity such as www-data, and chmod -R 777 is not a fix. If two sites have separate Unix owners on purpose, run a runner under each owner rather than collapsing that split.
Confirm the real document roots before you copy any example path. DigitalOcean’s current WordPress 1-Click documentation lists /var/www/html/ as the WordPress root on that image, and it ships WP-CLI, but an older or custom install may be elsewhere. That catalog currently describes a Caddy-based image; older and custom Droplets can use a different web stack, so don’t assume Apache or nginx is present, or issue a blanket web-server restart. Hetzner’s server and SSH docs do not establish a universal WordPress path, so use the document roots your own stack already uses. Inspect without dumping secrets:
stat -c '%U:%G %a %n' /var/www/html
stat -c '%U:%G %a %n' /var/www/html/wp-content
namei -l /var/www/html
Replace those paths with yours. Write permission on a directory does not mean every file inside it is writable. A served directory is also only a starting location, not a sandbox: picking a folder in the app, or asking the agent to inspect only, does not enforce read-only access. In the rebuilt Amp CLI, Amp no longer asks for permission before running tools, unless you have already configured permissions.
Sign into the same Amp account you use in the browser or the desktop app. The docs say a runner that doesn’t appear in the location picker is often still stopped, or signed in to a different account. You can check that the machine has spare memory and disk before you add a long-running process, with ordinary tools such as free -h, df -h and uptime. That is a capacity check, not an Amp sizing guide. On the Forge box I used, with three WordPress sites, the runner settled at about 295 MB RSS and peaked near 933 MB during startup and the self-update relaunch.
Back up the files and the database first
WordPress’s backup documentation is clear that a copy of the directory is not enough: you need the relevant files and the database, plus an off-server copy and a way to restore it. Never leave SQL dumps, configuration backups or archives in a directory the web server can serve. If you already have a restore-tested backup system, use that. DigitalOcean recommends powering the Droplet off before a snapshot when you care about consistency, and notes that many databases will not guarantee on-disk consistency unless the database service is stopped or the Droplet is powered off first. Hetzner likewise recommends powering the server off for consistent backups and snapshots, and says those copies do not include attached Volumes. Account for a managed or external database, and schedule any shutdown rather than taking the site down casually.
For a simple site, if WP-CLI, a matching PHP CLI and mysqldump are available to the maintenance user, you can export the database WordPress is configured to use and then archive the document root. Keep the backup directory private and outside every web root. On Laravel Forge the document root is usually /home/forge/<domain>/public; on a DigitalOcean WordPress 1-Click image it is often /var/www/html.
site_root=/home/forge/<domain>/public
backup_dir="$HOME/wordpress-backups/site-$(date -u +%Y%m%dT%H%M%SZ)"
install -d -m 700 "$HOME/wordpress-backups"
install -d -m 700 "$backup_dir"
(
umask 077
wp --path="$site_root" --quiet db export "$backup_dir/database.sql" \
--single-transaction --quick --skip-lock-tables
)
(
umask 077
set +e
tar -C "$site_root" --warning=no-file-changed -czf "$backup_dir/site-files.tar.gz" .
tar_status=$?
set -e
# GNU tar exits 1 if a file changed while it was read; the archive can still be valid.
if [ "$tar_status" -gt 1 ]; then
exit "$tar_status"
fi
)
test -s "$backup_dir/database.sql"
gzip -t "$backup_dir/site-files.tar.gz"
wp db export uses the connection in the WordPress configuration and hands the dump to mysqldump. The transaction flags above suit an InnoDB database; they do not make a consistent copy of non-transactional tables, and they do not protect against concurrent schema changes. The file archive and the database export are also sequential steps, not an atomic snapshot of both at once. This export is the configured database, not “the site you have open in a browser”: if several installations share one database, or this is Multisite, a full restore can affect the other sites. An archive of the document root also misses a wp-config.php kept above it, symlink targets and external uploads, so include those separately when they exist. Stop if a command fails, and verify recovery on an isolated copy rather than treating a successful export as proof that restore will work. When I archived a live wp-content, tar exited 1 with “file changed as we read it” and still wrote an archive that passed gzip -t. GNU tar uses exit 1 for that condition; --warning=no-file-changed quiets the message and does not change the exit code. If the shell is running with set -e, that exit 1 will abort the script before the gzip -t check unless you accept it as the snippet above does. On PHP 8.5, WP-CLI also flooded stderr with PHP Deprecated lines from its own dependencies, not from the site. Use --quiet when you script those commands, or keep stderr and ignore that deprecation noise.
Install the CLI and authenticate the service
If curl or CA certificates are missing, an administrator can install them on Ubuntu with sudo apt update and sudo apt install -y curl ca-certificates. That is not an instruction to upgrade PHP or the database. Then, as the maintenance user, run Amp’s documented installer for Mac, Linux and WSL:
curl -fsSL https://ampcode.com/install.sh | bash
"$HOME/.amp/bin/amp" version
The official installer places the binary in ~/.amp/bin and creates a link in ~/.local/bin. Using the direct path avoids depending on whether the current shell has reloaded its profile. If you set AMP_HOME, substitute that path throughout. Git is not required to register ordinary WordPress directories; don’t initialise empty repositories just to make discovery work.
For an interactive session, Amp documents amp login. A systemd user service is a non-interactive environment, and for those Amp documents an access token in AMP_API_KEY. Tokens from Settings start with sgamp_. The CLI rejects the short-lived session token that amp login stores, because it expires within an hour and cannot be refreshed from an environment variable. Put the access token in a mode-600 file outside every web root, for example ~/.config/amp/wordpress-runner.env, as a single line AMP_API_KEY=…, without export. Check it with stat -c '%a' ~/.config/amp/wordpress-runner.env; that must print 600. Other processes running as the same Unix user can still read that file, including PHP if it runs as that user. If a token is exposed, revoke it in Amp, write a replacement and restart the service.
This is the CLI, not the Linux desktop app. The Linux app is a separate amp-desktop package with a window, a tray icon and a built-in runner, and it expects a graphical desktop. A headless WordPress VPS wants the CLI runner.
Serve the WordPress directories, not just Git checkouts
This is the step that trips people up if they follow a generic “discover my repos” example. By default, a runner serves threads in the directory where you started it. --discover-dirs is narrower than it sounds: it serves every Git checkout up to two levels beneath the current directory. A WordPress site that was copied onto the server and never initialised as a Git or Jujutsu repository will not show up through discovery alone.
For directories that are not git checkouts, Amp documents --dir. Any existing directory works. Add --no-serve-cwd so the service’s own working directory does not appear in the site picker:
"$HOME/.amp/bin/amp" --no-tui --runner-id wordpress-vps \
--no-serve-cwd \
--dir /home/forge/<domain>/public \
--dir /home/forge/<another-domain>/public \
--remote-control-terminal \
--remote-control-portals
Replace those paths with the directories that actually hold your sites. On Laravel Forge, /home/forge/<domain>/public is the document root; a DigitalOcean WordPress 1-Click image often uses /var/www/html. Don’t create empty folders to make an example succeed. Runner IDs must be valid hostnames and are case-insensitive; Amp preserves the casing you provide. wordpress-vps is a valid ID, as is a more distinctive name such as wordpress-do-sgp.
On a headless runner authenticated with AMP_API_KEY, amp runner dirs add and amp runner dirs list failed for me with Error: Log in with amp login before using saved threads or runners. Put each site in a --dir flag on ExecStart and restart the unit. Those two commands expect a session where you have already run amp login, not the API-key service. New threads can only start in served directories. That chooses where a thread starts; it does not restrict the files or commands a running thread can reach, so the account you use still matters.
The Changes tab in a thread is available automatically when the runner directory is a Git or Jujutsu checkout. On a site that isn’t, you still get the files in the working directory while the runner is connected. Amp also no longer rolls back file changes when you edit or restore a message, so keep your own pre-change copies. If you later want reviewable diffs, that’s a reason to put the theme or a plugin in git, not a requirement for the runner to start.
Terminal, portals and a virtual desktop
Three optional flags, all documented on the runners page, change what you can do from a thread:
| Flag | What it adds |
|---|---|
--remote-control-terminal | A Terminal tab, so you can open a shell on the VPS from ampcode.com or the Amp apps. |
--remote-control-portals | A Portal tab for a dev server or preview the agent starts. Runner portals cannot be made public, and they have no review button. A thread, portal or desktop tab does not create a separate WordPress staging database, or turn production edits into staging work. |
--desktop | A Desktop tab. On Linux, the runner starts its own virtual desktop, so it does not capture a session on the machine. Sharing an existing GNOME, KDE or X11 session is not yet supported. Threads on one runner share that desktop. |
The Desktop and Terminal tabs connect over WebRTC, directly when the networks allow it, or through a TURN relay. If neither connects within five seconds, they fall back to Amp’s relay. You don’t have to open a public VNC, RDP or special Amp administration port for that path. Keep the inbound rules the sites and SSH already need, and remember the runner also needs working outbound connectivity, DNS and certificate validation. If you use DigitalOcean Cloud Firewalls, an empty outbound rule set blocks outgoing traffic. Hetzner Cloud Firewalls allow all outbound traffic when no outbound rules are defined, but adding even one outbound rule switches that direction to implicit deny. Host firewalls such as UFW are a separate layer. Amp does not publish a complete egress allowlist for every runner feature, so don’t treat “TCP 443 to one hostname” as a verified configuration for Terminal, Desktop and updates.
If you want the virtual desktop, check that the packages exist, then install Amp’s documented Linux dependencies and add --desktop to the runner command:
apt-cache policy labwc wlr-randr ffmpeg
sudo apt install -y labwc wlr-randr ffmpeg
"$HOME/.amp/bin/amp" runner desktop status
The virtual desktop is a headless labwc compositor at 1280×720, streamed by Waymote, the same stack orb desktops use. On x64 Linux, the runner downloads Waymote the first time --desktop starts, into ~/.amp/bin/waymote/, unless it is already on your PATH. On other architectures, install it from the Waymote releases. dbus-run-session and PulseAudio are optional; without PulseAudio the desktop streams video only. amp runner desktop status lists what the runner finds. A browser on that desktop is a separate package if you need one.
For a WordPress VPS you may not need the desktop at all. File edits and a terminal are enough for most theme and plugin work. Add it when you want to click through a graphical tool or watch a browser on the server, and try it on staging first if the production host is tight on memory.
Connect from your laptop or your phone
Once the runner is up, open ampcode.com, the Mac app, the Linux app, or the Amp iPhone and iPad apps, using the same account. The Mac app requires Apple silicon and macOS 26 or later; the web app is the alternative on a machine that does not meet that requirement. Create a thread, pick wordpress-vps in the runner or location picker, then pick the site directory. The agent’s commands and file edits happen on the VPS. Your local app is the interface for prompting, reviewing and using the Terminal, Portal or Desktop tabs.
Start with a narrow inspect request, and treat it as an instruction rather than enforced read-only access. On a site with WP-CLI, useful checks include:
wp --path=/home/forge/<domain>/public --quiet core version
wp --path=/home/forge/<domain>/public --quiet core verify-checksums
wp --path=/home/forge/<domain>/public --quiet --skip-plugins --skip-themes \
plugin list --fields=name,status,version --format=table
Core checksum verification compares WordPress core against WordPress.org; it does not verify a custom theme. Plugin listing can still load must-use plugins even when regular plugins and themes are skipped. On PHP 8.5 I used --quiet so WP-CLI’s own deprecation warnings did not flood the output. After an approved PHP edit, php -l on the changed file catches syntax errors, and then you still need to check the public page, including mobile layout, and the site’s usual logs. Take page caches and CDNs into account. For a live, non-versioned site, run one change-making task at a time: several threads share the same files and database. Stopping the runner does not stop the web server or the database, and it does not undo changes the agent already made.
You can also start a thread from a local terminal, with the interactive CLI talking to the VPS:
amp --executor runner:wordpress-vps \
--runner-dir /var/www/html
The path you pass to --runner-dir has to be an absolute path on the VPS that the runner already serves. To spawn a one-shot thread without the TUI, Amp documents amp -x with the same --executor runner:<id> form.
Keep it running after you disconnect
Amp’s docs are clear about the lifecycle: a runner runs on a machine you control, and Amp does not start, stop or pause that machine. If you start amp --no-tui in an SSH session and then close the session, the process goes with it unless something else is holding it up.
The Linux desktop app solves this for a graphical machine with a documented user service, amp-desktop-runner.service. That service is not what you want on a headless VPS. For the CLI runner, a systemd user unit is a standard way to keep the same command running after logout and at boot. This unit is an operator pattern, not a file Amp ships. Keep it as a systemd user service under the maintenance account; don’t casually convert the example into a system unit. Systemd does not run it through your interactive Bash profile, so use the explicit binary path and load the token file with EnvironmentFile. Aliases, NVM or other hooks in .bashrc will not apply unless you put the same paths on the unit’s PATH.
Create ~/.config/systemd/user/amp-runner.service as the maintenance user, substituting your site directories:
[Unit]
Description=Amp WordPress VPS runner
[Service]
Type=simple
WorkingDirectory=%h/.local/share/amp-wordpress-runner
Environment=PATH=%h/.amp/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin
EnvironmentFile=%h/.config/amp/wordpress-runner.env
ExecStart=%h/.amp/bin/amp --no-tui --runner-id wordpress-vps --no-serve-cwd --dir /home/forge/<domain>/public --dir /home/forge/<another-domain>/public --remote-control-terminal --remote-control-portals
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
Create the working directory first, for example with install -d -m 700 "$HOME/.local/share/amp-wordpress-runner". %h is systemd’s home-directory specifier. Add --desktop to ExecStart only if you installed the desktop dependencies and want that tab. Put any custom runtime paths, such as the site’s PHP CLI, on PATH. The private umask in the backup commands stays inside those subshells; don’t add UMask=0077, or a similarly tight mask, to the runner service without checking whether PHP and the web server can still read files the agent creates. Then enable lingering for that account and start the user service from a real SSH login, not a bare sudo -u that may not have a complete user session:
sudo loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now amp-runner.service
systemctl --user status amp-runner.service --no-pager
journalctl --user -u amp-runner.service -n 100 --no-pager
enable-linger is a systemd-logind setting: it keeps the user session available so the service can start at boot and survive logout. It does not grant extra site permissions. Restart=always will restart a crashed process; a restart loop is a fault to fix, not a sign the unit is healthy. The first start can also trigger a self-update and restart about eight seconds in, which looks like a crash in journalctl. Expect that one self-restart in the first minute, then treat a loop after that as a fault. After the first start, disconnect SSH and confirm a new thread still starts, then confirm startup after a scheduled reboot as a separate check. Neither check means an in-progress task will survive a VPS reboot. Long-running amp --no-tui runners also check for a new release about once an hour and install it, then restart into the new version once no thread is running, at most once every 12 hours, unless you turn auto-update off with amp.runner.autoUpdate.enabled or amp.updates.mode. Homebrew installs are the exception: they report a new version but wait for brew and a restart. After the self-update on my runner, a later log line said it was serving four directories when I had configured three. Trust the startup banner and the “Runner directories changed” event for the list of served paths.
A runbook, sharing and troubleshooting
Amp reads personal guidance from $HOME/.config/amp/AGENTS.md (and from $HOME/.config/AGENTS.md). Put operational notes there — document roots, the backup procedure, “don’t print credentials from wp-config.php” — rather than in a file inside a public document root. On Laravel Forge, /home/forge/<domain>/public is what the web server serves; the parent, /home/forge/<domain>, is the project root, and that is a reasonable place for a site AGENTS.md, a tools/ directory and private backups. If you serve only public/, threads start in what the web serves. If you serve the parent, the agent also starts with a view of the runbook and tooling, but your backups sit one level from that starting point. A served directory is still not a sandbox, so keep dumps out of any path the web server can reach. Those notes guide the agent; they do not impose an operating-system restriction. Merge with any existing guidance instead of overwriting it.
By default, threads see only the environment variables of the runner process. You can also turn on Secrets & Env Vars from ampcode.com with --amp-env, or with amp.runner.env.enabled set to true in the runner’s settings file. The next thread that starts in a directory gets the new values; threads already running keep the old ones until a new thread starts there.
--share lets other members of your workspace start threads on the runner. Amp prints a warning first, and the docs are blunt: those threads run as the operating-system user that started the runner, and they can read and change that user’s files and use its credentials. Only share a runner with people you trust to run code on that machine. On a production WordPress VPS, think hard before you turn this on. Stopping sharing does not undo changes already made, and it does not stop programs already started. Thread multiplayer is a separate setting.
Amp’s pricing page lists use of your own runners as free. Model and tool usage still follows the account’s plan. You also still pay DigitalOcean or Hetzner for the VPS and any snapshots or backups, and for Laravel Forge if you use it.
| If this happens | Check |
|---|---|
| Runner missing from the picker | The user service is running, the access token is valid, the Amp account matches, outbound connectivity works, and at least one served directory exists. Workspace admins can also disable Cross-Client Access, which hides runners from the web palette and blocks new threads on them from outside the CLI. |
| Terminal says access is disabled | The running service includes --remote-control-terminal. Reload the thread after a restart. |
| No Portal tab | Restart with --remote-control-portals, then reload the thread. |
amp runner dirs add tells you to run amp login | That is expected on a headless API-key runner. Add --dir to ExecStart and restart the unit. |
| A site is missing | The explicit --dir path, directory existence, and Linux traversal permissions. |
| The agent can read but not edit | Permissions on the actual file and its parent directories, not a global ownership change. |
| Commands work over SSH but fail in the service | The unit’s PATH, the explicit binary path, the PHP version and required environment variables. |
| Desktop unavailable | --desktop, installed dependencies, architecture-specific Waymote requirements, and the service logs. |
| A change seems absent | The correct domain and document root, then the site’s caches or CDN, and the files actually saved on disk. |
| The service restarts about eight seconds after the first start | A long-running runner can self-update and restart once in the first minute. Treat a loop after that as a fault. |
| The log says it is serving more directories than you configured | Trust the startup banner and the “Runner directories changed” event, not a later count after a self-update. |
WP-CLI prints a stream of PHP Deprecated lines | On PHP 8.5 those often come from WP-CLI’s own dependencies, not from your site. Use --quiet when scripting. |
| The env file is not mode 600 | stat -c '%a' ~/.config/amp/wordpress-runner.env must print 600. |
Useful service commands, all as the maintenance user, are systemctl --user status amp-runner.service --no-pager, journalctl --user -u amp-runner.service -n 100 --no-pager, systemctl --user restart amp-runner.service after a token or unit change (reload the daemon first if the unit file changed), and systemctl --user disable --now amp-runner.service to stop it and clear the automatic start. To add another non-git site, add another --dir to ExecStart, then reload and restart.
From a live Forge server
I ran this end to end on a Laravel Forge Ubuntu 24.04 box with 15 GB of RAM, PHP 8.5.12, WP-CLI 2.12.0 and MySQL 8.4.10, already hosting three WordPress sites. The official installer placed the binary at ~/.amp/bin/amp, a Settings access token starting with sgamp_ worked in a mode-600 EnvironmentFile, and loginctl enable-linger plus a systemd user unit with %h, --no-serve-cwd, --dir, --remote-control-terminal and --remote-control-portals brought the runner up.
| What I checked | What I found |
|---|---|
amp runner dirs add and dirs list on a headless API-key runner | Failed with Error: Log in with amp login before using saved threads or runners. Put each site in --dir on ExecStart and restart the unit. |
tar of a live wp-content | Exited 1 with “file changed as we read it”, and still wrote an archive that passed gzip -t. Under set -e the backup stopped before that check. Accept exit 1, then verify. |
| WP-CLI on PHP 8.5 | Flooded stderr with PHP Deprecated lines from its own dependencies, not from the site. Use --quiet when you script it, or keep stderr and ignore that deprecation noise. |
| The first start of a long-running runner | About eight seconds after I started it, the runner self-updated and came back, and journalctl made that look like a crash. One restart in the first minute is expected. |
| The directory count after that update | I had configured three directories, and after the self-update a later line said it was serving four. I go by the startup banner and the “Runner directories changed” event. |
| Memory | About 295 MB RSS once it settled, peaking near 933 MB during startup and the update relaunch, with three WordPress sites on the box. |
The official runners documentation is the page to keep: ampcode.com/docs/cli/runners. It also covers creating a new directory or an Amp-hosted project from the picker, and creating Git worktrees when several threads need to work in the same repository at once. Those extras help most once a site is in git. For WordPress trees that still aren’t, --dir, a recoverable backup, and a runner that stays up are the work that matters.
Discover more from Sulat AI & Tech News
Subscribe to get the latest posts sent to your email.




