Building a web proxy
This is documentation for building an interception-based web proxy with Scramjet. Plus a generator that hands you a working project configured the way you want it.
It fills the gap between short setup READMEs and reading every upstream package.
Two ways to use this:
You want a working proxy now. Go to Quickstart, or
open the builder at /build after starting this repository. The downloaded
project README gives the commands for your selected package manager.
You want to understand the stack. Start with How a proxy works and read the Concepts section in order. It builds up from the two jobs a proxy performs to why the current architecture looks the way it does.
The one thing to understand first
A proxy has two independent jobs, and nearly every confusing question comes from conflating them:
- Fetching bytes from a server the browser will not let you reach directly.
- Rewriting those bytes so every URL, script, and cookie inside them points back through the proxy.
Wisp, Bare, epoxy, and libcurl solve problem 1.
Scramjet solves problem 2. It is the engine.
They are chosen independently. "Should I use Scramjet or wisp?" is not a question. You use Scramjet over wisp.
What to read
Concepts
| Page | What it answers |
|---|---|
| How a proxy works | The four layers, and one request traced end to end |
| Proxy engines | What the rewriter does, and why this site documents Scramjet |
| Wisp vs Bare | The two tunnel protocols, and why wisp won |
| Transports | epoxy, libcurl, bare. What they are and how to choose |
| bare-mux and proxy-transports | What bare-mux is, and what replaced it |
| Cross-origin isolation | Why Scramjet needs COOP/COEP and what breaks without them |
Guides
| Page | What you build |
|---|---|
| Quickstart | A working proxy in about two minutes |
| Multiple tabs | Real tabs that keep their pages alive |
| Settings | Validated, persisted settings that cannot brick your app |
| URL parsing and history | An address bar that behaves like a browser's |
| Custom protocols | Internal pages on your own scheme |
| Cookies and sessions | Where logins live, and the three ways they break |
| Search engines | Which ones survive a proxy, and which only work in dev |
| Wiring Scramjet | Serving the engine, its service worker, and Wisp |
| Serverless deployment | An all-in-one deployment over Bare, and what it costs |
| Framework integrations | React, Astro, Fastify, Vite, Next.js, SvelteKit, Bun |
| Deployment | Hosting, HTTPS, and platform limits |
| Running a proxy | Bandwidth, blocking, abuse, and logging, after launch |
| Practices worth knowing | Shared storage, performance, accessibility |
Reference
| Page | What it answers |
|---|---|
| Config and flags | Every Scramjet option, what it does, what breaks |
| Plugins and hooks | The extension surface, and how to write against it |
| Site compatibility | Why a given site fails, and whether you can fix it |
Version matrix · Breaking changes · Troubleshooting · Official docs and licensing · Glossary
The builder
The builder at /build composes a project from parts. Every part is a real file
in builder/parts/, readable on its own. The generator only strips the blocks
you did not ask for and fills in a few names.
It asks about the stack:
| Question | Options |
|---|---|
| Language | TypeScript or JavaScript |
| Package manager | npm, pnpm, yarn, bun |
| Runtime | Node or Bun |
| Server framework | Express or Fastify |
| Frontend | Vanilla, React, or Astro + Preact |
| Build step | Vite, or none at all |
| Styling | Plain CSS, SCSS, or Tailwind |
| Transports | libcurl, epoxy, bare, or any mix |
It also asks about the features:
- Browser controls: back, forward, and reload wired to the frame's history
- Multiple tabs: one proxy session per tab, kept alive in the background
- Settings: validated and persisted, shown in a popup unless custom protocols are enabled
- Transport switching: any transport you shipped, at runtime, plus a custom Wisp server
- History: a visit log, separate from per-tab back and forward
- Bookmarks and history menus: popup overlays or navigable custom protocol pages
- Cloaking and custom protocol pages
- Quiet service worker: silence log, info and debug inside the worker
Choices that cannot work together are greyed out with the reason, rather than letting you pick them and quietly changing them afterwards. Picking an all-in-one serverless host selects the Bare transport, because those hosts cannot hold Wisp's WebSocket open; a static frontend may instead point at Wisp hosted elsewhere. See Serverless deployment.
Or from the terminal:
node builder/cli.js --out ./my-proxy --preset standard
node builder/cli.js --out ./my-proxy \
--language ts --runtime bun --server express \
--bundler vite --styling tailwind \
--features browserControls,tabs,settings,history
Generated projects are structured so that engine.ts is the only file that
talks to the proxy engine. Everything else, tabs, settings, history, is written
against a small interface, so swapping transports or upgrading the engine does
not touch your feature code.
The generated UI is a compact dark interface with a monospace typeface and plain controls. It works on narrow screens and is intended to be easy to edit.
Running this documentation locally
npm install
npm start
The documentation opens at / on the configured port. The guides render from
the same Markdown files that GitHub displays; the interactive builder is
local-site functionality.
Limits
- URL codecs are obfuscation, not encryption. The key is in the client bundle.
about:blankcloaking hides a URL from someone looking at your screen. It adds no network privacy. What an observer sees depends on whether the proxy uses Wisp or Bare; a managed-browser extension can still inspect the page.- Serverless hosting forces the Bare transport, which means your server terminates target TLS and WebSocket sites stop working. It is free to start and fine at small scale, but every proxied byte is billed egress, so it stops adding up as traffic grows. A static frontend pointed at Wisp on a cheap VPS avoids all of it.
- The published Scramjet docs currently describe the 1.x API while the repository has moved on. Breaking changes tracks the difference.
- This repository and generated projects use AGPL-3.0-only. See licensing for upstream links and the license notice.
If something here is wrong or out of date, open an issue or a PR.
Questions are best asked in the Night Network Discord, https://discord.gg/algebra, which is also where a lot of this ecosystem gets built.
Source: docs/index.md
Verified against Scramjet 2.0.67-alpha.2, controller 0.0.14, and Ultraviolet undefined on 2026-08-02. If this page and upstream disagree, upstream is right.