Web Gateway in practice ยท Part 2

Enable, Protect and Accelerate a Backend

Web Gateway serving a browser and protecting a private application stack
Martin Stecher Martin Stecher September 3, 2026

When one JSON file is no longer enough, a real backend earns its place. For another private service I chose a Vue frontend, a Go API and a relational database. Web Gateway still became the public face of the solution: it serves the frontend, proxies only the API and rejects most invalid sessions before Go ever sees them.

The point where simple stops being simple

The service from part 1 worked well with a Python policy and one JSON file because its data was small and its workflows were predictable. A different project had crossed that boundary. Now I needed a real database with complex queries, dynamic user management and parallel access by multiple users.

This time the familiar split was the right one: Vue for the browser, Go for the API and a relational database for persistent state. Each component had a clear responsibility. The remaining question was how to connect the stack to the Internet without adding another collection of moving parts.

Web Gateway was already my preferred public endpoint. What I had not used before was its web server and reverse proxy modes together for the same host.

One public host, two kinds of request

Browsers connect over HTTP/1.1, HTTP/2 or HTTP/3 to Web Gateway, which serves Vue files locally and proxies API requests over private H1C to Go and a database

Static frontend and private API behind one public gateway. Click to enlarge.

The URL already provided a natural dividing line. Everything below /api/ belongs to Go. The remaining paths belong to the Vue application. A small policy rule recognizes the API prefix and switches just those requests from local server mode into reverse proxy mode.

The production Vue build is simply a directory of HTML, JavaScript, CSS, fonts and images. Web Gateway serves those files directly. There is no Node.js process and no second web server in the production path. For client-side routes, the policy returns Vue's entry page and lets the application router take over in the browser. As simple as that!

API calls keep the same public origin, so the browser sees one coherent site. Internally, however, they take a different path: Web Gateway forwards them to the Go service on the private network. Cloud Fellows Web Gateway can therefore act as both web server and reverse proxy for the same hostname at the same time. The browser needs only one hostname, and the application avoids cross-origin complications.

Modern outside, deliberately simple inside

The public side again offers HTTP/1.1, HTTP/2 and HTTP/3 and uses ACME for a publicly trusted certificate and automatic renewal. That is the side exposed to browsers, network variation and protocol evolution.

The private side is much less exciting, on purpose. Web Gateway talks plain HTTP/1.1 to Go - H1C in the configuration - over a private Docker network. Both containers run on the same server. Adding another TLS connection or a multiplexed protocol there would not make the application meaningfully safer or easier to operate.

Only Web Gateway publishes host ports. Neither Go nor the database has a public port mapping. A request cannot simply bypass the gateway and address the backend directly; the Docker network is the only path to it.

A private network is a good boundary, but not enough

Hiding the backend from the Internet reduces the attack surface, but every public API request could still be forwarded through the gateway. Missing cookies, random cookie values, expired sessions and automated scans would all wake up Go and might cause a database lookup before being rejected.

The backend must remain the authority for authentication. But it does not have to spend time on a request that can already be proven invalid at the gateway.

The gateway becomes the initial cookie checker

Web Gateway rejects missing, modified or expired cookies and forwards only cookies with a valid HMAC and timestamp to the Go API for an authoritative database check

Fast rejection at the edge, authoritative validation in Go. Click to enlarge.

The session cookie created by Go contains random session material, an expiry timestamp and an HMAC covering both. The browser cannot change the expiry without invalidating the signature. A purpose-specific HMAC key is mounted read-only into the Go and Web Gateway containers.

That gives the policy engine a useful and deliberately limited capability. It can reject an API request when the cookie is missing, malformed, modified or visibly expired. The response comes from Web Gateway, and no backend connection or database query is needed. For a normal page request, the gateway can serve the login page itself.

If the signature and timestamp are acceptable, the request may proceed to Go. There, the real session check still happens against the database. Go can reject a revoked session, an inactive user or an operation for which the user lacks permission. The gateway pre-check is not a replacement for backend authentication; it is a filter in front of it.

This separation also keeps secrets narrow. Web Gateway receives the cookie-HMAC key it needs for verification, but it receives neither the application's general authentication secret nor database credentials.

Less backend work is a security feature

This setup also reduces the attack surface significantly: potential vulnerabilities in the backend are much less exposed. Before Web Gateway forwards an ordinary /api/ request, it must carry a currently valid-looking cookie.

But wouldn't we need to expose the API at least for the login page? Not really. The login page is a static HTML form, and Web Gateway's policy engine handles session establishment and cookie setting. In that workflow - and only there - the Python policy makes narrowly defined calls to Go to request a new session and cookie for the user. The browser never gets a pre-authentication /api/ route.

One HTTP/3 connection should not become a crowd of H1 connections

There is another, less obvious consequence of this protocol combination. HTTP/3 can carry many concurrent request streams over one QUIC connection. HTTP/1.1 cannot multiplex those requests in the same way, so a gateway may need several backend TCP connections when many streams are active at once.

The CDN Tsunami paper describes how that mismatch can be abused. An attacker opens many HTTP/3 streams and sends their bodies slowly. An eager intermediary creates an HTTP/1.1 backend connection for each stream, allowing one efficient public connection to consume a much larger number of scarce backend connections.

Web Gateway bounds that fan-out. A configurable limit caps how many concurrent HTTP/1.1 connections one downstream connection may open to one backend origin; the default is ten. Further requests wait in a bounded queue instead of creating connections without limit. This is exactly the kind of protection a modern H3-to-H1 gateway needs, even when it is not a CDN.

The same research review also reinforced a second boundary: decompressed request headers are capped before routing. Compact QPACK or HPACK input must not be allowed to expand into an unreasonable amount of work. Oversized field sections are rejected, and repeated abuse closes the connection.

One controlled public edge

The result has clear boundaries: the Internet reaches Web Gateway, Web Gateway reaches Go, and Go reaches the database. Although this solution has more components than the service in part 1, it is still simpler than a typical deployment today: one gateway that also serves the frontend, one API backend and one database.

This time Web Gateway did not replace the backend. It enabled, protected and accelerated it - and gave the complete application one small, controlled front door.

Read part 1, The Proxy That Became the Application, for the other end of the series: a service where the gateway, a Python policy and one JSON file were already enough.

More information about the product is available on the Cloud Fellows Web Gateway product page.

 

For questions and discussion please comment on our LinkedIn post