13 Feet Ladder: a self-hosted 12ft.io replacement that impersonates Googlebot
My own custom 12ft.io replacement
At a glance
- What is it?
- 13ft is a small Flask server that fetches an article as Google's crawler to get past paywalls, and it is meant to be run on your own machine. The approach works on some sites, fails on others, and the project's own README is honest about neither case.
- Who is it for?
- Adopt 13ft if you want a small self-hosted tool for reading individual articles and you are comfortable with a Flask app that sends a Googlebot user agent on your behalf. Do not adopt it if you need a guaranteed bypass, a supported product, or something that works on sites that detect crawler spoofing.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem 13ft solves, and the people it is aimed at
Paywalled articles are indexed by search engines. That is the whole premise. A site like Medium or the New York Times wants its text visible to Google so the article ranks, so it serves the full page to Google's crawler and the truncated version to everyone else. The README states this directly: the server "pretends to be GoogleBot (Google's web crawler) and gets the same content that google will get." 13ft is a self-hosted implementation of that idea, positioned by its author as a replacement for 12ft.io, which no longer exists as a public service.
The audience is narrow and specific. This is for someone who wants to read one article, not for someone building a reading product. The README says as much: "if you just wanna see one single article and move on with your day then this might be helpful." It also asks readers to support the creators they benefit from, which is a fair framing for a tool that exists in a grey area.
Because it is self-hosted, you control the request. Nothing about your reading history passes through a third party service, and there is no shared proxy that a publisher can block once and take down for everyone. That is the practical difference from the original 12ft.io, and it is the reason a project this small still has users.
How the Googlebot impersonation and the fallback chain work
The mechanism is a user agent swap plus a fetch. The server receives a URL, requests it while presenting itself as Googlebot, and returns the HTML it gets back. The README describes the newer behaviour as a loading or status page shown while the target article is fetched, with the server automatically trying multiple bypass methods ("multi-source fallbacks") if the primary approach fails. The README credits this feature to a contributor, @JRS1986, and says it improves compatibility with sites that use stronger paywalls or blocking. The README does not list what those methods are, in what order they run, or how a failure is reported to the user.
That gap matters more than it sounds. A fallback chain is only as good as its first two links, and if you cannot see which method produced the page you got, you cannot tell whether you are reading the crawler version, a cached version, or something else entirely. The architecture itself is thin by design: a Flask app, a request layer, and a template. The repository lists bs4 and requests in requirements.txt, so HTML parsing and HTTP fetching are the two moving parts. There is no database, no queue, and no cache layer visible in the top-level layout.
One consequence is that every request is a live fetch. The server does not store articles, so a page that is slow to load is slow every time, and a page that starts blocking Googlebot will start failing for you immediately.
Running 13ft with Docker Compose
The README's first install path is Docker. Clone the repository and bring the compose file up. The compose file in the repository pulls ghcr.io/wasi-master/13ft:latest and maps port 5000 on the host to port 5000 in the container, with restart set to unless-stopped.
git clone https://github.com/wasi-master/13ft.git
cd 13ft
docker compose upAfter the container starts, open the address the app prints and you get the input page shown in the README screenshots. Paste an article URL, submit, and the status page appears while the fetch runs. The README notes the image is also on Docker Hub, so `docker pull wasimaster/13ft` works as an alternative to building from the compose file.
If you would rather not use Docker, the Python path is short. From the cloned directory, install the four dependencies and run the portable entry point.
cd app/
python -m pip install -r requirements.txt
python portable.pyThe README warns that if this fails you should retry with `py`, then `python3`, then `py3`. Gunicorn is the production option: `python -m gunicorn 'portable:app'`. For a specific bind address, the README gives a venv recipe using `FLASK_APP=app/portable.py flask run --host=127.0.0.1 --port=9982`.
There is also a bookmarklet route for Chrome, which turns the current tab into a 13ft request. The README's snippet rewrites the location to your instance with the encoded current URL, and the instructions come from contributor @barakplasma. It is a one-line script, and it is the fastest way to use the tool once your instance is up.
javascript: (function () {window.location.href="https://localhost:5000/" + encodeURIComponent(window.location.href);})();The Googlebot trick stops working when publishers stop trusting it
Crawler detection is not static. Publishers have known for years that a user agent string is trivially forged, so the more serious paywall vendors verify the request against Google's published IP ranges before serving the full page. Against those sites, 13ft's primary method returns the same truncated article a normal browser gets. The README's fallback chain is the project's answer to this, but the README does not say which sites it rescues or how often it succeeds.
There is a second failure mode that is easy to miss: the tool is a general-purpose fetcher pointed at arbitrary URLs. The README does not document any allowlist, rate limit, or authentication on the server. If you expose it on a public hostname, you are running an open proxy that will fetch whatever URL anyone sends it, with your IP address attached. The systemd and Apache examples in the README both bind the Flask app to 127.0.0.1 and put a reverse proxy in front, which is the right shape, but the README does not describe adding authentication to that proxy.
Finally, this is the wrong tool if you need the article in a structured form or at scale. There is no API documented for bulk extraction, no export format, and no way to feed a list of URLs. It reads one page at a time, in a browser, on demand.
How 13ft differs from archive and cache services
The closest alternative in practice is a web archive such as the Wayback Machine. The difference is where the content comes from. An archive serves a snapshot that was captured at some earlier point, so the page may be missing images, comments, or updates, and a recent article may have no snapshot at all. 13ft fetches the live page at the moment you ask, which means you get the current version, but you get it only if the publisher's crawler handling lets you through.
A reader-mode or text-extraction extension is another comparison point, and the difference there is the target. Reader modes reformat a page you already have access to; they do not get past a paywall, because the truncated HTML is all the browser received. 13ft's entire value is in changing what the server sends back, not in how it is displayed.
The README's own comparison is to 12ft.io, and the meaningful difference is hosting. 12ft.io was a single public endpoint, which made it a single point of failure and a single target. 13ft is a container you run, so the failure is yours to debug, and so is the traffic.
Maintenance, upgrades, and what the MIT licence covers
The repository is not archived, and the last push was on 2026-07-18. Releases are infrequent: v0.4.0 landed on 2026-05-11 and v0.4.1 on 2026-07-12, after a long gap back to v0.3.4 on 2024-10-27. The Dockerfile still carries version labels of 0.3.4 and a creation date of 2023-10-31, which suggests the image metadata is not updated in lockstep with releases. If you pin by tag rather than by digest, check what the tag actually points at.
Upgrade cost is low in the ordinary case. The dependency list is four packages (bs4, flask, gunicorn, requests), and the Docker image is built from python:3.13.7-alpine. There is no schema and no stored state, so replacing the container is the whole upgrade. The one thing to re-check after an upgrade is localisation: the README documents a LOCALE environment variable with built-in locales en, de, fr, and ko, and locale files under app/locales. A new release can add or rename keys in those files.
The project is MIT licensed, which permits commercial use, modification, and redistribution provided the copyright notice and permission notice are kept. That is a statement about the licence text, not about the legality of what you fetch with the software. The README itself asks users to support the creators whose work they read.
Running 13ft as a systemd service behind a reverse proxy
The README includes a systemd unit and an Apache virtual host example for people who want the app running permanently on a server rather than started by hand. The unit sets APP_PATH and FLASK_APP, runs as www-data, restarts on failure after 10 seconds, and starts Flask bound to 127.0.0.1 on port 22113. The Apache example listens on port 22114, terminates TLS, and proxies to that local port, so the Flask process is never directly reachable.
Environment=APP_PATH=/var/www/paywall-break
Environment=FLASK_APP=app/portable.py
ExecStart=/bin/bash -c "cd ${APP_PATH};${APP_PATH}/venv/bin/flask run --host=127.0.0.1 --port=22113"Those two files are the most operationally useful part of the README, because they encode the deployment shape the maintainer expects: local bind, proxy in front, TLS at the edge. What they do not include is access control. If you follow them literally, the proxy serves the tool to anyone who can reach port 22114. Adding HTTP basic auth or an IP restriction at the Apache layer is left to you, and the README does not mention either.
Editorial conclusion
Adopt 13ft if you want a small self-hosted tool for reading individual articles and you are comfortable with a Flask app that sends a Googlebot user agent on your behalf. Do not adopt it if you need a guaranteed bypass, a supported product, or something that works on sites that detect crawler spoofing. Before you commit, verify which of the multi-source fallback methods actually run in your deployment, and check the LOCALE value you want against the built-in locale files in app/locales.
Frequently asked questions
Did 12ft.io get shut down?
The 13ft README describes itself as a self-hosted replacement for 12ft.io and notes that it works with websites 12ft.io does not work with, which implies the original service is no longer a usable option. The README does not give a date or reason for any shutdown.
What are some alternatives to the 12ft paywall?
13ft is itself presented as one, and the README positions it against 12ft.io directly. The other option the README implies is running the same Flask app yourself under Docker or a systemd unit rather than relying on a public endpoint.
Can 12ft.io bypass all paywalls?
The README does not claim universal coverage, and it describes a multi-source fallback chain that exists precisely because the primary Googlebot approach fails on some sites. Sites that verify crawler requests against Google's IP ranges are the obvious case where the method does not hold.
Official sources
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.
[](https://hysenlabs.com/projects/wasi-master-13ft)