🕺 Welcome to Berghain: Where Only Valid Browsers Get the Backend Party Started! 🎉
Berghain is your trusty SPOE-Agent, guarding the entrance to the backend like a seasoned bouncer. This Go and HAProxy-powered tool ensures that only the coolest and most valid browsers can access the exclusive party happening on the other side.
With Berghain in charge, you can be confident that your backend is reserved for the true VIPs of the internet, keeping out any uninvited guests. It's like the bouncer of the web world, ensuring that your resources are reserved for the browsers that really know how to dance!
Seeing a "Request on Hold" screen, or want to understand what visitors experience? See the help page for troubleshooting, browser compatibility and privacy details.
- None (Simple JS execute)
- POW
- Turnstile
- hCaptcha
- reCAPTCHA v2
- Simple Captcha (Including Sound)
The turnstile, hcaptcha and recaptcha (v2 checkbox) level types render the provider widget
on the challenge page and exchange its response token for a Berghain cookie after verifying it
against the provider:
default:
levels:
- duration: 12h
type: turnstile # or hcaptcha / recaptcha
sitekey: <your sitekey>
secret: <your secret>Things to know when enabling a captcha level:
- The Berghain agent verifies tokens against the provider's
siteverifyendpoint, so it needs outbound HTTPS access. Verification fails closed: if the provider is unreachable, the challenge fails and the visitor can retry.verify_urloverrides the endpoint, e.g. forrecaptcha.net. - Challenge verification does a network round-trip, so the SPOE challenge group needs a larger
timeout processingthan the validate path. The example config runs the two groups as separate agents (berghainat 100ms,berghain_challengeat 6s) for this reason. - The token is only accepted when the provider-reported hostname matches the request identity
(subdomains included). Provider test keys report a fixed hostname, so tests can set
skip_hostname_check: true; production setups should never need it. - Visitors' browsers load the widget script from the provider's domain. If you serve the challenge
page with a Content-Security-Policy, allow the provider in
script-srcandframe-src.
To start berghain locally you can follow these easy steps:
For Debian / Ubuntu: apt install npm
- Run
npm installinsideweb/ - Run
npm run buildinsideweb/ - Run
haproxy -f examples/haproxy/haproxy.cfg - Run
go run ./cmd/spop/. -config cmd/spop/config.yaml
For production use, generate a random secret to place in the Berghain configuration file using openssl rand -base64 32.
examples/haproxy/haproxy-ua-policy.cfg is an alternative
HAProxy example that challenges known AI crawlers and browser-like clients, allows selected tools and public API/feed
paths, and tarpits selected scraper libraries. The default example remains User-Agent neutral.
Customize the maps under examples/haproxy/maps/ and the public_endpoint ACLs before use.
Map keys are lowercase literal substrings without whitespace. User-Agent headers are trivial to spoof, so this policy is
traffic shaping only; it must not protect authenticated or otherwise sensitive endpoints.
To run the project using Docker, follow these steps:
-
Build the Docker images:
docker-compose build
-
Start the services:
docker-compose up
-
Access the application:
- Test App: http://localhost:8080
Make sure to have Docker and Docker Compose installed on your system before running these commands.
The frontend build supports two environment variables for operator-owned customisation:
VITE_ENTRYPOINTselects an alternative HTML entrypoint.VITE_HOOKSselects a JavaScript file whose labeled phase blocks are compiled directly into the default challenge flow.
Paths may be absolute or relative to the web/ directory when running the npm scripts. The file
can contain shared static imports and any of these optional labeled blocks:
initruns after the DOM is ready and before the cookie check.challengeStartruns after the challenge is fetched and can accesschallenge.successruns before the success UI and can accesschallengeandcountdown.failureruns before the failure UI and can accesschallenge,countdown, andresult.
For example:
init:{
await document.fonts.ready;
document.title = "Verifying - Example";
}
challengeStart:{
console.info("Challenge started", challenge.t);
}
success:{
document.documentElement.dataset.challengeStatus = "success";
console.info("Challenge passed", {challenge, countdown});
}
failure:{
document.documentElement.dataset.challengeStatus = "failure";
console.error("Challenge failed", {challenge, countdown, result});
}This example is available as web/examples/challenge-page.js.
cd web
VITE_HOOKS=./examples/challenge-page.js npm run build:defaultThe phase blocks are inserted as scoped statements during Vite's transform phase; there is no runtime
hook registry or callback dispatch. Static imports are resolved relative to the customization file. Top-level
await is supported and delays the next challenge step. Exceptions follow the surrounding challenge flow's
normal error behavior, and omitted phase blocks are ignored.
The file cannot export values or contain other top-level statements. Phase blocks cannot use top-level
var, dynamic import(), import.meta, or top-level arguments. Environment path changes require
restarting the Vite development server.
Thanks to @NullDev and @arellak, as they did most of the frontend work.