docs/contributing/testing-development-urls.md
Ghost supports configurations that differ from the normal
http://localhost:2368 setup. Test the configuration that matters to your
change rather than rebuilding the whole development environment for every URL
edge case.
Use an HTTPS tunnel when you need to open the site or Admin on a phone, tablet, or another computer. This avoids maintaining local DNS, assigning a static IP, and installing a local certificate authority on every test device.
With pnpm dev running, install and authenticate the
ngrok agent, then run:
ngrok http 2368
Open the HTTPS forwarding URL on the other device. Ghost Admin is available at
/ghost/ on the same URL.
The development Caddy gateway trusts the forwarded protocol supplied by ngrok,
Tailscale Funnel, and similar HTTPS tunnel agents. Tunnel the gateway on port
2368, not an individual Admin or public-app development server, so requests
continue through the same routing used by the normal development environment.
Tunnel URLs are normally temporary. The site can be inspected without changing
Ghost's configured URL, but links generated by Ghost will still use
http://localhost:2368. When the behaviour under test depends on absolute
URLs, create ghost/core/config.local.json with the forwarding URL and restart
the development environment:
{
"url": "https://your-forwarding-domain.example"
}
Do not commit config.local.json. Remove the override when the test is
finished.
Treat a public tunnel URL as temporary public access to the local site. Do not use production data or credentials, and stop the tunnel after testing.
Use the manual environment below when you need to inspect all three behaviours
in a browser. It runs a local TLS proxy in front of Ghost Core and uses
.localhost hostnames, which resolve to the loopback address without editing
/etc/hosts or running a DNS server.
Install Caddy if it is not already
available. Create ghost/core/config.local.json:
{
"url": "https://site.localhost:8443/blog/",
"admin": {
"url": "https://admin.localhost:8443/blog/"
}
}
The site URL supplies the /blog/ subdirectory. The separate Admin URL uses
the same subdirectory because Ghost's Admin and API routes remain underneath
the configured site path.
Start the Docker services with the URL-testing Compose override:
DEV_COMPOSE_FILES="-f docker/dev-url-testing/compose.yaml" \
pnpm nx run ghost-monorepo:docker:up
The override exposes Ghost Core directly on port 2369. This is intentional:
the extra Caddy process must connect directly to Ghost so Express can trust its
forwarded HTTPS header.
In another terminal, start the local TLS proxy:
caddy run --adapter caddyfile \
--config docker/dev-url-testing/Caddyfile
Caddy may ask for the system password the first time so it can install its local certificate authority. Open:
https://site.localhost:8443/blog/https://admin.localhost:8443/blog/ghost/Check the behaviour affected by the change, including redirects, generated
links, API requests, cookies, and assets. A request to the site hostname's
/blog/ghost/ path should redirect to the Admin hostname.
This configuration serves the Admin assets available from Ghost Core. It does not use the normal Admin Vite/HMR route because that development server's startup probe assumes the default root URL. Use the automated tests below for focused development, then use this manual environment for final browser verification.
Press Ctrl+C to stop Caddy. Stop the Docker services with the same override:
DEV_COMPOSE_FILES="-f docker/dev-url-testing/compose.yaml" pnpm docker:down
Delete ghost/core/config.local.json before returning to pnpm dev; otherwise
Ghost will continue using the alternate URLs. The file is ignored by Git.
Ghost Core keeps its advanced URL configuration coverage in
ghost/core/test/e2e-frontend/advanced-url-config.test.js. It currently covers
subdirectory routing and redirects to a separate Admin origin, and is the
default place to add HTTP-level coverage for behaviour that depends on:
Run the file from ghost/core:
pnpm test:single test/e2e-frontend/advanced-url-config.test.js
Add a focused case there when the behaviour can be verified through Ghost's
HTTP responses, redirects, generated URLs, or routing. Tests elsewhere in
ghost/core/test/ also set url and admin:url through the shared test config
helpers when a lower-level test is sufficient.