Skip to main content

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
Enforced on every way out

--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 *.suffix for its subdomains.
  • *.internal.example covers app.internal.example but not internal.example itself. 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.

Which one to use

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 HOST1 or PORT1 matches anything. An empty HOST2 or PORT2 leaves 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.

Was this page helpful?
Last updated