Library / SDK
nfriedly/node-unblocker avatar
nfriedly/node-unblocker

unblocker is a Node.js proxy that rewrites remote pages so they work behind a proxy

Web proxy for evading internet censorship, and general-purpose Node.js library for proxying and rewriting remote webpages

520 stars1,128 forksJavaScriptAGPL-3.0

At a glance

What is it?
unblocker began as a censorship-evasion CGI proxy and became a general-purpose Node.js library for proxying and rewriting remote webpages, streaming responses without unnecessary buffering and rewriting links and cookies so the proxied site still works. It suits a developer who needs a rewriting proxy in Node, and it does not handle the modern login flows that large sites have moved to.
Who is it for?
unblocker fits a Node developer who needs to proxy and rewrite arbitrary remote pages, since the express-compatible API and the middleware hooks make it a few lines to mount, and the rewriting handles the details that make naive proxies break. It does not fit a project that needs to proxy a modern single-sign-on site, because the README states OAuth login forms and postMessage are not likely to work and that advanced sites such as Discord and YouTube are unsupported.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it became, and why it is not just a proxy

Unblocker was originally a web proxy for evading internet censorship, similar to CGIproxy, PHProxy or Glype but written in node.js. It has since morphed into a general-purpose library for proxying and rewriting remote webpages.

The rewriting is the part that matters, and it is what separates this from a reverse proxy that forwards bytes. A naive proxy that fetches a page and returns the body produces a page whose relative links point at the origin instead of the proxy, whose cookies are set for the wrong domain and whose form submissions go somewhere else.

Three mechanisms fix that, and the README describes each one. The script uses pretty URLs which, besides looking pretty, allow links with relative paths to just work without modification, so a link such as `<a href="path/to/file2.html"></a>` continues to work as written.

Links that are relative to the root, such as `<a href="/path/to/file2.html"></a>`, are handled without modification by checking the referrer and 307 redirecting them to the proper location in the referring site, although the proxy does attempt to rewrite these links to avoid the redirect.

And cookies are proxied by adjusting their path to include the proxy's URL, with extra work to ensure they remain intact when switching protocols or subdomains.

On top of that, all data is processed and relayed to the client on the fly without unnecessary buffering, which the README credits for making unblocker one of the fastest web proxies available.

Mounting it, and the one ordering constraint

The library is installed from npm.

bash
    npm install --save unblocker

Unblocker exports an express-compatible API, so using it in an express application is trivial.

javascript
    var express = require('express')
    var Unblocker = require('unblocker');
    var app = express();
    var unblocker = new Unblocker({prefix: '/proxy/'});

    // this must be one of the first app.use() calls and must not be on a subdirectory to work properly
    app.use(unblocker);

    app.get('/', function(req, res) {
        //...
    });

    // the upgrade handler allows unblocker to proxy websockets
    app.listen(process.env.PORT || 8080).on('upgrade', unblocker.onUpgrade);

Three details in that snippet are load-bearing rather than incidental.

The prefix cannot be the root path. The configuration comment says that '/' is not recommended due to a few edge cases, and the middleware has to be one of the first app.use() calls and must not be on a subdirectory to work properly. That is because the rewriting depends on seeing every request that could be part of a proxied page, and a middleware mounted after your own routes would miss them.

The upgrade handler is what makes websockets work, since app.listen's upgrade event is where the HTTP upgrade handshake is handled and unblocker has to intercept it.

And the configuration object is where the prefix lives, so the same instance serves under whatever path you mount it at.

The README points to examples/simple/server.js for a complete example, and notes that usage without express is similarly easy with a pointer to the same file.

The configuration surface and what each option controls

The configuration options are listed with their defaults, and the list is short enough to read as a summary of what the library does.

js
{
    prefix: '/proxy/',  // Path that the proxied URLs begin with. '/' is not recommended due to a few edge cases.
    host: null, // Host used in redirects (e.g `example.com` or `localhost:8080`). Default behavior is to determine this from the request headers.
    requestMiddleware: [], // Array of functions that perform extra processing on client requests before they are sent to the remote server. API is detailed below.
    responseMiddleware: [], // Array of functions that perform extra processing on remote responses before they are sent back to the client. API is detailed below.
    standardMiddleware: true, // Allows you to disable all built-in middleware if you need to perform advanced customization of requests or responses.
    clientScripts: true, // Injects JavaScript to force things like WebSockets and XMLHttpRequest to go through the proxy.
    processContentTypes: [ // All  built-in middleware that modifies the content of responses limits itself to these content-types.
        'text/html',
        'application/xml+xhtml',
        'application/xhtml+xml',
        'text/css'
    ],
    httpAgent: null, //override agent used to request http response from server. see https://nodejs.org/api/http.html#http_class_http_agent
    httpsAgent: null //override agent used to request https response from server. see https://nodejs.org/api/https.html#https_class_https_agent
}

The middleware arrays are the extension point, and standardMiddleware is the switch that turns off everything built in, which is the thing to reach for when you want full control rather than layering on top.

clientScripts injects JavaScript to force things like WebSockets and XMLHttpRequest to go through the proxy. That matters because without it, a page's own AJAX calls would go straight to the origin and bypass the proxy entirely, which breaks both the rewriting and any interception you have set up.

processContentTypes is the list the built-in content-modifying middleware limits itself to, covering HTML in its three flavours and CSS. Anything else passes through unmodified, which is why a JSON API endpoint proxied through unblocker is not rewritten.

The two agent options let you override the HTTP and HTTPS agents used to make the upstream request, which is how you set proxy support, keep-alive behaviour or a custom TLS configuration.

One environment variable matters: setting `process.env.NODE_ENV='production'` enables more aggressive caching on the client scripts and potentially other optimisations in the future.

Custom middleware, and the shape of the data object

Unblocker middleware are small functions that allow you to inspect and modify requests and responses, and the majority of the internal logic is implemented as middleware, so you can write custom middleware to augment or replace the built-in middleware.

A custom middleware should be a function that accepts a single `data` argument and runs synchronously. To process request and response data you create a Transform Stream to perform the processing in chunks and pipe through this stream. To respond directly to a request, you add a function to `config.requestMiddleware` that handles the `clientResponse`.

The data object for requestMiddleware looks like this.

js
{
    url: 'http://example.com/',
    clientRequest: {request},
    clientResponse: {response},
    headers: {
        //...
    },
    stream: {ReadableStream of data for PUT/POST requests, empty stream for other types}
}

So you get the target URL, the original client request, the response object, the headers and a stream. The stream is populated only for PUT and POST bodies, which is the detail that matters if you are filtering uploads.

The example in the README is an access control filter, which is the clearest possible use of the hook.

js
function validateRequest(data) {
    if (!data.url.match(/^https?:\/\/en.wikipedia.org\//)) {
        data.clientResponse.status(403).send('Wikipedia only.');
    }
}
var config = {
    requestMiddleware: [
        validateRequest
    ]
}

Two rules follow from the design. If any piece of middleware sends a response, no further middleware is run. And after all requestMiddleware has run, the request is forwarded to the remote server with the potentially modified url, headers and stream.

responseMiddleware receives the same data object, so the same shape applies to the response side.

What it does not support, stated plainly

The limitations section is the most useful part of the README for anyone considering this, because it names the failure cases.

Although the proxy works well for standard login forms and even most AJAX content, OAuth login forms and anything that uses postMessage, which names Google and Facebook as examples, are not likely to work out of the box. The README says this is not an insurmountable issue, but that it is not one the author expects to have fixed in the near term.

More advanced websites, such as Roblox, Discord, YouTube and Instagram, do not currently work, and there is no timeframe for when these might be supported.

That last sentence matters for how you plan a deployment. If you are building an internal proxy for a specific set of sites, the list of unsupported ones tells you whether you will spend an afternoon or a quarter. If you are building something general, you are building on top of a tool that will not carry the sites that matter most.

The reason for the OAuth limitation is structural rather than a missing patch. An OAuth flow involves a redirect to an identity provider, a callback with a code, and a token exchange, and the proxy's rewriting of URLs and cookies interferes with the state that has to survive the round trip. The same applies to postMessage, where the page and an embedded frame have to agree on an origin and the proxy changes the origin.

There is one workaround in the repository for the YouTube case, an example that detects YouTube video pages and replaces them with a custom page that just streams the video. That is the shape of the answer for a specific site: an example in the examples folder rather than a change to the library.

Patches are welcome, including both general-purpose improvements to go into the main library and site-specific fixes to go in the examples folder.

The package, the tests and the licence

The package.json describes unblocker as a web proxy for evading internet censorship and a general-purpose library for rewriting remote websites, with the version at 2.3.2 and Node 16.17 or newer required by the engines field.

The dependency list explains what the rewriting is built on. express at 5.x provides the handler API, the cookie and set-cookie-parser packages handle the cookie path rewriting, iconv-lite deals with character encodings, tld gives the public suffix information needed to tell a subdomain from a domain, mime and content-type handle the response types, and lodash and async are the general utility libraries.

The scripts show how the project is tested, and there is a performance harness alongside the spec suite.

code
    "test": "npm run lint && npm run test-spec",
    "test-spec": "node --test test/*spec.js",
    "test-perf": "node test/performance.js"

So the specs run on the Node built-in test runner rather than a framework, there is a separate lint step, and the performance test is a script you run deliberately.

The licence is AGPL-3.0, with the LICENSE.txt file at the repository root and the license field matching. For a library that other people run as a service, the AGPL is the term that obliges you to publish modifications, and that is a decision any deployment has to make knowingly.

The repository root holds lib/ as the implementation, test/ for the specs, examples/ with a folder per scenario including simple, express, youtube, whitelist, blacklist, custom-user-agent and replace_snippet, plus a .gitmodules file, a changelog.md and a CONTRIBUTING.md.

The last push to the repository was on 2026-10-18 and there are no GitHub releases listed, so the version in package.json is the reference point.

Where unblocker is the wrong choice

Four cases argue against it.

The first is a modern single-sign-on site. The README says OAuth login forms and postMessage are not likely to work, and names Google and Facebook. If your use case is proxying a service that authenticates with OAuth, this library is the wrong tool and no amount of configuration will change that.

The second is a large consumer platform. Discord, YouTube, Instagram and Roblox are named as unsupported, with no timeframe. A proxy whose purpose is to reach those services is not going to get there.

The third is a production service with security requirements. This is a library designed to fetch arbitrary remote URLs on behalf of the client and rewrite the responses, which is the shape of an open proxy, and open proxies are a well known abuse vector. If you expose it to the internet without your own allow list, you have published a proxy that strangers will use for their own purposes, and the requestMiddleware hook in the README is how you prevent that.

The alternative in the same direction is to write the rewriting yourself against a general purpose HTTP client, or to use a reverse proxy configured for your specific upstream. The difference in approach is that unblocker has already solved the tedious parts, relative links, root-relative links via referrer and 307, cookie paths across protocol and subdomain changes, and injecting the client script so that XMLHttpRequest and WebSocket traffic goes through the proxy too. Doing that yourself is a week of work and a set of bugs that only show up on the sites you did not test. For a fixed set of upstream sites, a reverse proxy with a rewrite map is simpler and far less exposed. For an open-ended set of sites, unblocker is the shortcut.

Editorial conclusion

unblocker fits a Node developer who needs to proxy and rewrite arbitrary remote pages, since the express-compatible API and the middleware hooks make it a few lines to mount, and the rewriting handles the details that make naive proxies break. It does not fit a project that needs to proxy a modern single-sign-on site, because the README states OAuth login forms and postMessage are not likely to work and that advanced sites such as Discord and YouTube are unsupported. Mount it with `app.use(unblocker)` as one of the first routes, keep the prefix off the root path, and test against the specific sites you care about before you rely on it.

Frequently asked questions

What does the unblocker package do?

It began as a web proxy for evading internet censorship, similar to CGIproxy or PHProxy but written in Node.js, and has since become a general-purpose library for proxying and rewriting remote webpages. It processes and relays data to the client on the fly without unnecessary buffering.

How do I use unblocker with express?

Install it with `npm install --save unblocker`, create a Unblocker instance with a prefix such as `/proxy/`, and mount it with `app.use(unblocker)`. The README warns that it must be one of the first app.use() calls and must not be on a subdirectory to work properly, and the upgrade handler passed to app.listen's upgrade event is what allows it to proxy websockets.

What are the main unblocker configuration options?

The prefix for proxied URLs, the host used in redirects, requestMiddleware and responseMiddleware arrays for custom processing, standardMiddleware to disable all built-in middleware, clientScripts to inject JavaScript forcing WebSockets and XMLHttpRequest through the proxy, processContentTypes listing the content types the built-in content-modifying middleware touches, and httpAgent and httpsAgent overrides.

Which sites does unblocker not work with?

OAuth login forms and anything using postMessage are not likely to work out of the box, with Google and Facebook given as examples, and the README says it does not expect to have fixed that in the near term. More advanced sites such as Roblox, Discord, YouTube and Instagram do not currently work and there is no timeframe for when they might be supported.

What licence is unblocker under?

AGPL-3.0, with the LICENSE.txt file at the repository root and the license field in package.json matching. The package version is 2.3.2 and it requires Node 16.17 or newer.

Official sources

  1. Issues
  2. License: AGPL-3.0
  3. nfriedly/node-unblocker on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nfriedly-node-unblocker.svg)](https://hysenlabs.com/projects/nfriedly-node-unblocker)

Was this useful?