How to Run WordPress in a Cloud Sandbox With an HTTPS URL
Updated October 27, 2026 .md
Short answer: Run the official wordpress and mariadb images with Docker Compose in a Sandboxer sandbox, bind WordPress to 127.0.0.1 and host it with sbx host --localhost. Install with WP-CLI using the hosted HTTPS URL as the site URL. The image already trusts X-Forwarded-Proto, so login, wp-admin and permalinks work.
What we ran#
Everything below ran on production on October 27, 2026, in one small sandbox (2 vCPU, 4 GB): WordPress 7.1.3 from the wordpress:7.1-php8.3-apache image, MariaDB 11.8 and WP-CLI 2 from wordpress:cli-2-php8.3. The requests came from a laptop, single runs unless noted.
| Step | Result |
|---|---|
sbx new --type small |
3.2 s to ready |
docker compose up -d (pulls both images) |
19.8 s |
wp core install |
2.0 s |
wp plugin install classic-editor --activate |
3.1 s |
| Post page through the hosted URL, 20 requests | 288 ms median (279 to 583 ms) |
| Same page from inside the sandbox | 69 ms median |
/wp-admin/ through the hosted URL, 10 requests |
335 ms median |
| Memory used by both containers | about 220 MB |
sbx stop --wait then sbx resume |
2.2 s stop, 1.3 s resume, memoryRestored: true, still logged in |
Resume with --type default (boots from disk) |
Both containers back in under 10 s, site and wp-admin 200 |
1. The Compose file#
Docker and Compose are preinstalled in every sandbox. This file follows the official WordPress image docs and adds three things: WordPress is published on 127.0.0.1 only, both services restart on their own, and a cli service runs WP-CLI against the same files and database.
# wp/compose.yaml
x-wp-env: &wp-env
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
services:
db:
image: mariadb:11.8
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wp
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_RANDOM_ROOT_PASSWORD: "1"
volumes:
- db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
retries: 30
wordpress:
image: wordpress:7.1-php8.3-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
environment: *wp-env
volumes:
- wp:/var/www/html
cli:
image: wordpress:cli-2-php8.3
profiles: ["cli"]
user: "33:33"
depends_on:
- wordpress
environment:
<<: *wp-env
HOME: /tmp
volumes:
- wp:/var/www/html
volumes:
db:
wp:Why these choices:
127.0.0.1:8080:80. The sandbox has a public IPv6 address. Its firewall (ufw) is closed except SSH, but Docker can bypass ufw rules on some setups. Binding to127.0.0.1keeps WordPress reachable only through the hosted URL, whatever the firewall does.user: "33:33"oncli. The CLI image runs as a different user ID than the Apache image, whose files belong towww-data(33). Without it, WP-CLI can't write plugins into the shared volume.- The
clienvironment repeats the database variables. The image'swp-config.phpreads them at run time, so WP-CLI needs them too. - The
service_healthycondition waits for MariaDB before WordPress starts.healthcheck.shships in the mariadb image.
2. Start it#
Copy the folder in, write random passwords to files (they are never printed), and start the stack:
sbx new --name wordpress --type small
id=sb_p4esgbgh3g # the id that sbx new printed
sbx scp -r ./wp $id:/home/user/
sbx exec $id --timeout 600 "cd wp && umask 077 \
&& echo DB_PASSWORD=\$(openssl rand -hex 16) > .env \
&& openssl rand -hex 12 | tr -d '\n' > admin-pass \
&& docker compose up -d --quiet-pull \
&& docker compose ps --format 'table {{.Service}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'" Container wp-db-1 Healthy
Container wp-wordpress-1 Starting
Container wp-wordpress-1 Started
SERVICE IMAGE STATUS PORTS
db mariadb:11.8 Up 5 seconds (healthy) 3306/tcp
wordpress wordpress:7.1-php8.3-apache Up Less than a second 127.0.0.1:8080->80/tcp3. Host the port before you install#
WordPress stores its own address in the database. So get the URL first, then install with it.
sbx host $id 8080 --localhost --title wordpress{"ok":true,"type":"port.hosted","sandboxId":"sb_p4esgbgh3g","port":8080,"url":"https://crisp-copper-lime-8080.box.sandboxer.com?_token=<token>","access":"private","isProtected":true,"localhost":true}--localhost makes the sandbox relay to 127.0.0.1:8080, which is where Compose published it. The URL is private: keep the _token secret. See Hosting for public URLs.
4. Install WordPress with WP-CLI#
Save this as wp/install.sh and copy it in. It passes the admin password from the file, so it never shows in the output:
#!/bin/bash
# usage: bash install.sh https://<subdomain>-8080.box.sandboxer.com
set -e
cd ~/wp
docker compose run -T --rm cli wp core install --url="$1" --title="Sandbox blog" \
--admin_user=admin --admin_email=admin@example.com --skip-email \
--admin_password="$(cat admin-pass)"sbx scp wp/install.sh $id:/home/user/wp/install.sh
sbx exec $id --timeout 300 "bash wp/install.sh https://crisp-copper-lime-8080.box.sandboxer.com"
sbx exec $id "cd wp && docker compose run -T --rm cli wp option get siteurl"Success: WordPress installed successfully.
https://crisp-copper-lime-8080.box.sandboxer.comUse the base URL, without ?_token=. Our first try used --prompt=admin_password to read the password from stdin. WP-CLI echoed the password back in its output, so we switched to the form above.
Read the password with sbx exec $id "cat wp/admin-pass" when you log in.
5. The two settings that make it work behind a proxy#
siteurl and home. WordPress redirects every request to the host in its home option. To show what goes wrong, we set both options to http://localhost:8080, as a local install would:
/ 301 -> https://crisp-copper-lime-8080.box.sandboxer.com:8080/
/wp-admin/ 302 -> https://localhost:8080/wp-login.php?redirect_to=…&reauth=1The home page sent the browser to port 8080 on the hosted name, and wp-admin sent it to localhost. Neither loads. If you moved a site in, fix every stored URL with wp search-replace:
sbx exec $id "cd wp && docker compose run -T --rm cli wp search-replace \
http://localhost:8080 https://crisp-copper-lime-8080.box.sandboxer.com --all-tables --report-changed-only"Table Column Replacements Type
wp_options option_value 2 PHP
Success: Made 2 replacements.Both paths then returned 200.
HTTPS behind the proxy. TLS ends at the Sandboxer edge, so Apache in the sandbox sees plain HTTP. The hosting proxy keeps the original Host header and sends X-Forwarded-Proto: https. The official image's wp-config.php already turns that header into $_SERVER['HTTPS'] = 'on':
sbx exec $id "docker compose -f wp/compose.yaml exec -T wordpress grep -n -A2 HTTP_X_FORWARDED_PROTO /var/www/html/wp-config.php"122:if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
123- $_SERVER['HTTPS'] = 'on';
124-}We commented out line 123 to see what happens without it:
src="http://crisp-copper-lime-8080.box.sandboxer.com/wp-includes/js/hoverintent-js.min.js?ver=2.2.1"
/wp-admin/ 302 -> https://crisp-copper-lime-8080.box.sandboxer.com/wp-admin/
login POST 302 -> https://crisp-copper-lime-8080.box.sandboxer.com/wp-login.phpPages linked their scripts over http://, which browsers block as mixed content. wp-admin redirected to itself in a loop, and the login form sent us back to the login page. If you build your own image or use another WordPress setup, add those three lines near the top of wp-config.php. WordPress documents the same fix in Administration over SSL. We restored the file before going on.
6. Log in and open wp-admin through the URL#
We checked the private URL and the login with curl from a laptop. $U is the base URL, $T the token, and pass holds the admin password:
curl -s -o /dev/null -w "no token: %{http_code}\n" $U/
curl -s -c jar -b jar -o /dev/null -w "with token: %{http_code}\n" "$U/?_token=$T"
curl -s -c jar -b jar -o /dev/null $U/wp-login.php # sets WordPress's test cookie
curl -s -c jar -b jar -o /dev/null -w "login: %{http_code} -> %{redirect_url}\n" \
--data-urlencode log=admin --data-urlencode "pwd@pass" \
--data-urlencode "redirect_to=$U/wp-admin/" --data testcookie=1 $U/wp-login.php
curl -s -b jar $U/wp-admin/ | grep -o '<title>[^<]*'no token: 401
with token: 302
login: 302 -> https://crisp-copper-lime-8080.box.sandboxer.com/wp-admin/
<title>Dashboard ‹ Sandbox blog — WordPressThe token request sets a cookie and redirects to /, so a browser needs the token only once. The home page's links all use https://crisp-copper-lime-8080.box.sandboxer.com.
7. Pretty permalinks and a plugin with WP-CLI#
The image ships Apache with mod_rewrite and writes the standard .htaccess, so changing the structure is one command:
sbx exec $id --timeout 300 "cd wp
docker compose run -T --rm cli wp rewrite structure '/%postname%/'
docker compose run -T --rm cli wp post create --post_title='Hello from a sandbox' --post_status=publish --porcelain
docker compose run -T --rm cli wp plugin install classic-editor --activate
docker compose run -T --rm cli wp plugin list --fields=name,status,version"Success: Rewrite structure set.
Success: Rewrite rules flushed.
6
Installing Classic Editor (1.7.0)
…
Plugin 'classic-editor' activated.
Success: Installed 1 of 1 plugins.
name status version
akismet inactive 5.7.2
classic-editor active 1.7.0
hello inactive 1.7.2Through the hosted URL, /hello-from-a-sandbox/ returned 200, /?p=6 returned 301 to it, /wp-json/wp/v2/posts/6 returned the post with "link": "https://crisp-copper-lime-8080.box.sandboxer.com/hello-from-a-sandbox/", and /wp-admin/plugins.php listed Classic Editor.
The post page took 288 ms median through the URL and 69 ms median from inside the sandbox. Most of the difference is the network path from our laptop to the sandbox, which depends on where you are.
8. Stop and resume#
sbx stop $id --wait
sbx resume $id
sbx exec $id "cat /proc/uptime; cd wp && docker compose ps --format '{{.Service}} {{.Status}}'"164.74 283.15
db Up 2 minutes (healthy)
wordpress Up 2 minutessbx info showed memoryRestored: true. The containers never restarted, and the same cookie jar still opened /wp-admin/plugins.php with no new login. The URL and token stayed the same.
A resume boots from disk when no machine with the same CPU type is free or when you resize. To test that path, we stopped again and resumed with sbx resume $id --type default:
type default memoryRestored False resumeReason resized
2.59 6.20
db Up 9 seconds (healthy)
wordpress Up 9 secondsAn uptime of 2.6 seconds shows a fresh boot. Docker started with the VM and restart: unless-stopped brought both containers back. The post, the plugin and the login cookie all survived, because they live in the volumes and the database. See Snapshots for the resume rules.
9. Clean up#
sbx delete $id --yesAfter the delete, sbx info returned not_found and the URL returned 404. A small sandbox costs $0.018/hour while it runs and nothing while it is stopped.
FAQ#
Why does WordPress redirect to localhost or to port 8080?#
The home and siteurl options hold a different address than the one in your browser. Install with --url set to the hosted URL, or run wp search-replace from the old URL to the new one across all tables.
Why does wp-admin loop or load without styles behind the URL?#
WordPress doesn't know the request was HTTPS. The official image handles this with an X-Forwarded-Proto check in wp-config.php. Other setups need the same three lines, and Sandboxer's proxy always sends that header.
Can I make the site public?#
Yes: sbx host $id 8080 --localhost --public. Switching between private and public mints a new token for private links. Keep the WordPress admin password strong, because anyone can then reach /wp-login.php.
Do I need the web installer?#
No. wp core install does the same in about 2 seconds and lets you script the site URL, admin user and plugins. We didn't test the browser installer.
Is small enough?#
For a test site, yes. Both containers used about 220 MB of the 4 GB, and wp-admin pages answered in about a third of a second through the URL. Size up for heavy plugins or imports.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.