Skip to content
Guides · Recipes

How Do I Preview a Hugo or Astro Site on an HTTPS URL in a Sandbox?

Updated October 27, 2026 .md

Short answer: Run hugo server with --baseURL set to the hosted URL, --appendPort=false and --liveReloadPort 443, or run astro dev with server.allowedHosts set to .box.sandboxer.com, then sbx host the port. Live reload works through the private URL. A 300-page build took about 0.15 s in Hugo and 4.2 s in Astro.

How to host a port is covered in give a sandbox port an HTTPS URL, and per-PR previews in a preview URL for every pull request. This page covers what Hugo and Astro need on top of that. Their dev servers assume they're on localhost, so behind an HTTPS proxy they break in ways that don't show up on your laptop.

What we ran#

Everything below ran on production on October 27, 2026, in one small sandbox (2 vCPU, 4 GB RAM, $0.018/hour). Times are from one sandbox. Build times are three runs. Tokens are replaced with <token>.

Step Result
sbx new --type small 3.2 s to ready
Install Hugo v0.168.0 extended from the GitHub .deb worked (not timed)
hugo --minify, 338 pages 150, 175, 152 ms (Hugo's own "Total in")
npm create astro@latest (minimal template, with install) 26.3 s
Upgrade Node.js 22 to 24 for Astro 7.4.2 13 s
astro build, 301 pages 4.22 s reported, 4.6–5.5 s wall clock with npx
Edit a file to the reload message on the laptop, through the private URL Hugo 357 ms, Astro 227 ms (each includes a ~200 ms sbx exec round trip)
sbx stop --wait, then sbx resume 2.2 s and 1.3 s, memoryRestored: true, both URLs 200

Hugo#

We installed the extended edition from the .deb on Hugo's GitHub releases, one of the methods in its Linux install docs. Ubuntu's apt package is usually older.

We saved these lines as hugo-setup.sh in the sandbox (sbx new --name blog --type small gave us sb_5698hpwhgl, so the commands below use id=sb_5698hpwhgl) and ran it with sbx exec $id --timeout 600 "bash hugo-setup.sh":

set -e
V=$(curl -fsSL https://api.github.com/repos/gohugoio/hugo/releases/latest | jq -r .tag_name | sed 's/^v//')
curl -fsSLo /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${V}/hugo_extended_${V}_linux-amd64.deb
sudo dpkg -i /tmp/hugo.deb >/dev/null
hugo version
hugo new site blog >/dev/null && cd blog && hugo new theme demo >/dev/null && echo "theme = 'demo'" >> hugo.toml
hugo v0.168.0-e27a6760c5547237f87d353aebe0580feadb20f4+extended linux/amd64 BuildDate=2026-10-13T16:42:50Z VendorInfo=gohugoio

What goes wrong with the defaults#

We first started hugo server --bind 0.0.0.0 --port 1313 and hosted port 1313. The page loaded through the private URL. Two things were broken:

curl -s -b jar https://ample-vital-ember-1313.box.sandboxer.com/ | grep -o '<script[^>]*livereload[^>]*>'
curl -s -b jar https://ample-vital-ember-1313.box.sandboxer.com/index.xml | grep -o '<link>[^<]*</link>' | head -2
<script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;path=livereload" data-no-instant defer>
<link>http://localhost:1313/</link>
<link>http://localhost:1313/posts/posts-107/</link>
  • Live reload never connects. The script opens its WebSocket on port 1313, but the hosted URL only answers on 443. Our test of https://ample-vital-ember-1313.box.sandboxer.com:1313/ got Could not connect to server.
  • Absolute URLs point at localhost:1313. The RSS feed, the sitemap and anything a theme builds with .Permalink or absURL link to your own laptop. Relative links in the page work, so this is easy to miss until you click a canonical link or test a feed.

The fix: three flags#

The hugo server flags that fix it are --baseURL (the hosted URL), --appendPort=false (keep :1313 out of generated links) and --liveReloadPort 443, which Hugo's help describes as the setting for HTTPS proxies. $SANDBOXER_SUBDOMAIN is set inside every sandbox, so the unit can be written without copying the name. A systemd unit keeps the server running after a reboot:

sbx exec $id 'sudo tee /etc/systemd/system/hugo-preview.service >/dev/null <<EOF
[Unit]
Description=Hugo preview server
After=network-online.target

[Service]
User=user
WorkingDirectory=/home/user/blog
ExecStart=/usr/local/bin/hugo server --bind 0.0.0.0 --port 1313 --baseURL https://${SANDBOXER_SUBDOMAIN}-1313.box.sandboxer.com/ --appendPort=false --liveReloadPort 443
Restart=always

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now hugo-preview'
sbx host $id 1313 --title Hugo
https://ample-vital-ember-1313.box.sandboxer.com?_token=<token>

After that, the same two checks:

<script src="/livereload.js?mindelay=10&amp;v=2&amp;port=443&amp;path=livereload" data-no-instant defer>
<link>https://ample-vital-ember-1313.box.sandboxer.com/</link>
<link>https://ample-vital-ember-1313.box.sandboxer.com/posts/posts-107/</link>

To check live reload without a browser, we opened wss://…-1313.box.sandboxer.com/livereload from a laptop with the __sbx_auth cookie (see the WebSocket section of the hosting guide for how to get it), sent the LiveReload hello, then edited a post with sbx exec:

pre-edit {"command":"hello","protocols":["http://livereload.com/protocols/official-7"],"serverName":"Hugo"}
edit sent after 201 ms
+357ms {"command":"reload","path":"/x.js","originalPath":"","liveCSS":true,"liveImg":true}

The reload arrived about 150 ms after the edit command returned, and the page then served the new text. In a browser, opening the token link sets the cookie and live reload works with no extra steps. The Hugo server process used about 100 MB of RAM with 300 posts.

Astro#

We followed Astro's install guide with the minimal template:

sbx exec $id --timeout 600 'npm create --yes astro@latest site -- --template minimal --install --no-git --yes --skip-houston'

That took 26.3 s and installed Astro 7.4.2. The next command failed:

Node.js v22.23.3 is not supported by Astro!
Please upgrade Node.js to a supported version: ">=22.23.4"

Astro 7.4.2's package.json asks for Node.js >=22.23.4, one patch release above the Node.js 22 in the sandbox image on that day (what's preinstalled). We installed Node.js 24 from NodeSource in 13 s:

sbx exec $id --timeout 600 'curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - >/dev/null && sudo apt-get install -y nodejs >/dev/null && node --version'
v24.22.1

What goes wrong with the defaults#

astro dev --host 0.0.0.0 --port 4321, hosted on 4321, answered 403 through the private URL:

Blocked request. This host ("ample-vital-ember-4321.box.sandboxer.com") is not allowed.
To allow this host, add "ample-vital-ember-4321.box.sandboxer.com" to `server.allowedHosts` in vite.config.js.

The message mentions vite.config.js, but in an Astro project the setting goes in astro.config.mjs as server.allowedHosts. There is also an --allowed-hosts flag on astro dev. A leading dot allows every subdomain, so the config keeps working on a fork, which gets a new subdomain.

The fix#

// astro.config.mjs
// @ts-check
import { defineConfig } from "astro/config";

export default defineConfig({
  site: "https://ample-vital-ember-4321.box.sandboxer.com",
  server: {
    host: "0.0.0.0",
    port: 4321,
    allowedHosts: [".box.sandboxer.com"],
  },
});

With host and port in the config, the unit's command is just npx astro dev. We used the same unit as for Hugo, with WorkingDirectory=/home/user/site and ExecStart=/usr/bin/npx astro dev. The page then returned 200 in 0.28 s.

site is what Astro.site returns, so canonical links and anything an integration builds from it use the hosted URL. With <link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} /> in the page, the built dist/index.html contained:

<link rel="canonical" href="https://ample-vital-ember-4321.box.sandboxer.com/">

Set site back to your real domain before a production build.

Hot module reload needed no change. The injected /@vite/client had hmrPort = null, so the browser connects back on the page's own host and port 443. We connected to the HMR socket through the private URL with the vite-hmr protocol, the cookie and the wsToken from /@vite/client, then changed the page's <h1>:

pre-edit {"type":"connected"}
edit sent after 214 ms
+227ms {"type":"full-reload"}

Build times#

Both projects had about 300 pages of Markdown, roughly 3 KB each, built on the small sandbox.

Hugo v0.168.0 Astro 7.4.2
Pages 338 (300 posts plus taxonomy pages) 301
Build 150, 175, 152 ms 4.22 s reported; 5.3, 4.6, 5.5 s wall clock
Scaffold and install one .deb, no dependencies (not timed) 26.3 s, plus 13 s for Node.js 24

These are themeless starters. Real themes, image processing and integrations add to both. To keep node_modules between projects, save a set-up sandbox with sbx snapshot and start new ones from it.

After stop, resume and fork#

sbx stop --wait took 2.2 s and sbx resume 1.3 s with memoryRestored: true. Both dev servers were still running, and both URLs answered 200 with the cookie from before the stop. If a resume lands on a machine with a different CPU type, the sandbox boots from disk instead. The enabled systemd units then start the servers again, but the browser has to reconnect.

A fork gets a new subdomain and no hosted ports. Host the ports again on the fork. For Hugo, rewrite the unit, because --baseURL holds the old name. For Astro, update site. allowedHosts already covers the new name.

FAQ#

Why does my Hugo page load but never reload on save?#

The live reload script connects to the port in its port= parameter, 1313 by default, and the hosted URL only listens on 443. Start hugo server with --liveReloadPort 443 and --appendPort=false.

Why does Astro answer 403 "Blocked request"?#

Vite, which Astro's dev server uses, rejects Host headers it doesn't know. Add allowedHosts: [".box.sandboxer.com"] under server in astro.config.mjs, or pass --allowed-hosts.

Does live reload work on a private URL?#

Yes. Opening the token link once sets a cookie, and the browser sends it on the WebSocket. We tested both Hugo's LiveReload socket and Astro's HMR socket that way.

Which sandbox size should I use?#

small was enough for both 300-page sites. Hugo used about 100 MB of RAM while serving. Use default for large Astro sites with heavy integrations or image processing.

Do I need --localhost when I host the port?#

No, not with these commands. Both servers were bound to 0.0.0.0. If you leave Astro's host unset, it listens on localhost only. Then host the port with --localhost, or set host.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

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.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

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.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

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.

Read the guide
Best E2B alternatives in 2026, ranked

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.

Read the guide