Decide what the tunnel may reach
A tunnel can reach whatever the machine running it can reach. These options narrow that down to what your tests actually need, and steer requests to a different destination without touching your tests.
- Since v5.0
- HTTP, CONNECT and WebSocket
--allow-hosts, --fast-fail-regexps and --localhost-policy apply to plain HTTP requests, HTTPS CONNECT tunnels and WebSocket upgrades alike. A refused request gets a 403 that says which rule refused it.
Allow only named hosts
--allow-hosts names what the tunnel is for. Everything else is refused.
java -jar testingbot-tunnel.jar --allow-hosts 'staging.example.com,*.internal.example'
- An entry is an exact host or
*.suffixfor its subdomains. -
*.internal.examplecoversapp.internal.examplebut notinternal.exampleitself. List the apex separately if you need it. - Omit the option and any host is reachable, as in 4.x.
This is the safer of the two approaches. A deny list only stops destinations somebody thought of in advance. An allow list keeps a tunnel on a CI runner from becoming a route into everything else on that network.
Fast-fail domains
--fast-fail-regexps refuses requests to hosts matching any of a comma-separated list of regular expressions. The browser gets an immediate answer instead of waiting on a request that would go through the tunnel.
# skip analytics and ad networks to speed up page loads
java -jar testingbot-tunnel.jar -F '.*google-analytics\.com,.*doubleclick\.net'
New in 5.0: prefix an entry with ! to make an exception. This blocks everything except ok.com and its subdomains:
java -jar testingbot-tunnel.jar -F '.*,!ok\.com'
An exception has to cover a whole host or subdomain, so it cannot be satisfied by a name that merely ends with it: ok.com.attacker.net is not excepted by !ok\.com.
To speed tests up by skipping third-party noise, use --fast-fail-regexps. To limit what the tunnel may reach, use --allow-hosts. You can combine them: a host has to pass both.
Localhost policy
--localhost-policy deny refuses tunnel traffic that would reach this machine's own loopback interface.
java -jar testingbot-tunnel.jar --localhost-policy deny
Use it for a tunnel that is meant to reach a staging network, and nothing running on the machine itself, such as a shared CI runner or a jump host. The check is applied to the address actually dialled, not only to the name in the URL, so a DNS answer that points a public-looking name at 127.0.0.1 cannot walk past it.
The default is allow, which is what you want when testing a development server on your laptop. The Selenium relay is not affected. For how cloud browsers reach your local server, see Can I use localhost?
Remap destinations with --connect-to
Dial a different host or port than the request asks for, while leaving the URL, the Host header and the TLS SNI name untouched. The format matches curl's --connect-to: HOST1:PORT1:HOST2:PORT2.
# test https://www.example.com against the staging instance
java -jar testingbot-tunnel.jar --connect-to 'www.example.com:443:staging-lb.internal:443'
# send every request on port 80 to a local dev server on 3000
java -jar testingbot-tunnel.jar --connect-to ':80:127.0.0.1:3000'
- An empty
HOST1orPORT1matches anything. An emptyHOST2orPORT2leaves that half of the destination unchanged. - Comma-separate several rules. The first rule that matches wins.
- Bracket IPv6 literals:
example.com:443:[::1]:8443.
This lets a test drive the production hostname against another instance, with the same virtual host and certificate name the real site uses. Editing /etc/hosts is the usual alternative, but it needs root, affects every process on the machine and cannot change a port.
Custom DNS
--dns resolves hostnames through servers you name instead of the system resolver. Useful for split-horizon setups where the machine's default resolver does not know internal names.
java -jar testingbot-tunnel.jar --dns 10.0.0.53,10.0.0.54:5353
| Option | Effect |
|---|---|
| --dns <server[,server]> | Host or host:port, comma separated. The first is primary and the rest are tried in order when it does not answer. Falls back to the system resolver if none can. |
| --dns-round-robin | Spread queries across the servers instead of treating the first as primary, for a pool whose members are equal. |
| --dns-timeout <seconds> | How long to wait for each server. Default 5. |
Connection timeouts
| Option | Default | Effect |
|---|---|---|
| --http-dial-timeout <seconds> | 5 for the API, 15 for tunnelled connections | How long to wait for a TCP connection to a target or upstream proxy. Lower it where unreachable hosts should fail fast. Also bounds the SSH control connection. |
| --http-idle-timeout <seconds> | 120 for HTTP, 300 for CONNECT | How long a connection may sit with no traffic before it is closed. Raise it for long-lived idle connections such as long polling. |
Reading refusals and errors
When the tunnel cannot or will not complete a request, it says why three ways: the HTTP status, an X-TestingBot-Error header, and a one-line explanation in the body that shows up in the cloud browser's window.
| X-TestingBot-Error | Status | Meaning |
|---|---|---|
| not-allowed | 403 | The host is not in --allow-hosts. |
| denied-by-fast-fail | 403 | Blocked by --fast-fail-regexps. |
| denied-localhost | 403 | Blocked by --localhost-policy deny. |
| dns-error | 502 | The hostname could not be resolved from the tunnel machine. |
| connection-refused | 502 | Nothing is listening on that port. |
| connection-timeout | 504 | Timed out connecting to the target. |
| host-unreachable | 502 | No network route to the target from the tunnel machine. |
| tls-error | 502 | The TLS handshake with the target failed. |
| upstream-proxy-* | 502 | The upstream proxy was unreachable, rejected the credentials, or refused the destination. |
| loop-detected | 508 | The request targets the tunnel's own proxy port. |
The same reason string is the reason label on the proxy error counter in Prometheus metrics, so a spike on a dashboard and a header in a bug report name the same thing.