Open-source project
LowderPlay/cheburcheck avatar
LowderPlay/cheburcheck

Cheburcheck: a Rust checker for Russian domain block lists

Сайт для проверки блокировок в России

727 stars19 forksRustBSD-3-Clause

At a glance

What is it?
Cheburcheck answers one question, whether a domain or IP currently appears on a Russian block list. The self hostable stack pairs a Rocket API with a SvelteKit frontend, prefix tree lookups over Quad9 DNS over HTTPS, and separately versioned components.
Who is it for?
Adopt Cheburcheck if you want a self hosted answer to a single question, whether a specific domain or IP is currently listed as blocked, and you can accept an answer that is only as fresh as your cache download on that host. Do not adopt it as a way to reach blocked content, as a general purpose corporate filtering product, or as a source you will cite in a compliance report without checking the freshness of the databases yourself.
Can I use it commercially?
Yes. BSD-3-Clause 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 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

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

Editorial analysis

A lookup for one narrow question about Russian blocks

Cheburcheck.ru is an open source service for checking whether a domain or IP address appears on the block lists of Roskomnadzor and on several popular blocked CDN providers. The repository description calls it a site for checking blocks in Russia, and its declared topics are censorship-resistance, russia and vpn. It answers a narrow question for people who need it answered fast: is this name or address listed right now.

The public instance is cheburcheck.ru and the code is BSD-3-Clause licensed, which is the point of interest for organizations that want to run their own copy instead of sending every lookup to somebody else's server. The project is not archived, and the last push was on 2026-10-19, so the code you read is recent. What it is not is a tunnel, a proxy, or a client that carries your traffic anywhere. The traffic flow stops at the answer, which makes the security and operational footprint small compared with the tools it superficially resembles.

Quad9 over HTTPS first, prefix trees second

The mechanism is short enough to hold in your head, and the order of the steps is the interesting part. A user types a domain or IP into the form on the site. The server then performs the DNS query through Quad9 over DNS over HTTPS, and the project gives the reason for that choice: it lets the service get current answers and get around local restrictions. Only after resolution does the lookup against the blocking lists happen, and that matching is done with prefix trees rather than flat lists of individual addresses, so a whole network range can be tested without holding every single address in memory.

The result then travels back through the API and is rendered by the SvelteKit SSR frontend. Resolution before matching is the part with real consequences. The answer describes the address the name currently points at, not the string you typed, so a domain that was repointed after being blocked can come back clean. That also means the result depends on the resolver path your server can reach, which is where the whole thing becomes fragile in some networks rather than in others.

One command brings up nginx, the frontend, the API and the broker

The project documents a single command for a local run, and it starts the whole topology at once, including the rmqtt broker that the compose file lists as a dependency of nginx.

sh
docker compose up --build

By default nginx listens on http://localhost:8080. If you want the host side of that mapping on port 80 instead, the published port comes from an environment variable:

sh
HTTP_PORT=80 docker compose up --build

The compose file passes the environment through to the Rocket service, and this is where a first run tends to get interesting, because the database variables are not given defaults:

yaml
    environment:
      ROCKET_ADDRESS: "::"
      ROCKET_PORT: 8000
      DATABASE_URL: "${DATABASE_URL}"
      CLICKHOUSE_URL: "${CLICKHOUSE_URL}"
      CLICKHOUSE_POOL_SIZE: "${CLICKHOUSE_POOL_SIZE:-4}"
      CLICKHOUSE_ACQUIRE_TIMEOUT_SECONDS: "${CLICKHOUSE_ACQUIRE_TIMEOUT_SECONDS:-5}"
      CLICKHOUSE_QUERY_TIMEOUT_SECONDS: "${CLICKHOUSE_QUERY_TIMEOUT_SECONDS:-10}"
      DATABASE_CACHE_DIR: /var/cache/cheburcheck/databases
      DATABASE_RETRY_INTERVAL_SECONDS: "${DATABASE_RETRY_INTERVAL_SECONDS:-300}"
      MQTT_HOST: rmqtt
      MQTT_PORT: 11883

Everything except the two database URLs and the MQTT credentials has a fallback value here, so an unset DATABASE_URL or CLICKHOUSE_URL gives you a container that starts and then cannot talk to a database. The documentation does not spell out a local setup without those two services, so plan for a real database and a real broker rather than expecting a zero dependency demo.

nginx keeps the API paths and the page paths apart

Routing is described with a short rule set. Requests under /api/v1/*, /agency/* and /whitelist/* are proxied into Rocket, and everything else goes to the SvelteKit SSR frontend. The container definitions follow the same split, with nginx mounting its configuration read only and declaring its dependencies explicitly:

yaml
  nginx:
    image: docker.io/nginx:1.27-alpine
    depends_on:
      - frontend
      - website
      - rmqtt
    ports:
      - "${HTTP_PORT:-8080}:80"

The frontend container exposes port 3000 with HOST set to 0.0.0.0 and NODE_ENV set to production, while the Rocket service listens on ROCKET_ADDRESS "::" and ROCKET_PORT 8000. So you have two backends behind one public address, and the API and the pages can be logged and scaled independently without sticky sessions. The practical note for anyone self hosting is that the shipped configuration speaks plain HTTP inside the compose network, so TLS has to be terminated somewhere outside it. The documentation does not describe a certificate or a TLS entry in the compose stack, which means that piece is yours to add.

The cache directory and the five minute retry after a failed download

Verification databases are cached on disk in a database-cache/ directory. The path can be redirected with DATABASE_CACHE_DIR, the main refresh interval is set with DATABASE_INTERVAL_SECONDS, and the delay before retrying after a download error is DATABASE_RETRY_INTERVAL_SECONDS, which defaults to 300 seconds. A failed list download therefore does not take the service down. It only pushes the next attempt five minutes out, and the previously cached data keeps serving answers in the meantime.

The lists themselves come from 123jjck/cdn-ip-ranges, antifilter.download and antifilter.network, and on top of those the project builds its own allow lists through the Cheburcheck Reporter. The consequence for an operator is that a stale cache is a normal operating state rather than an error, and the freshness of any single answer depends on how the download schedule behaves on your host and on whether those three sources are reachable from it at all. If you are running this in a network where those hosts are blocked, you are also in the network where the tool is most wanted, and the project does not document a mirror or an offline import path.

The probe, reporter and reports crates widen the scope

The Cargo workspace lists five members: probe, querying, reporter, reports and website. The querying module is the one that checks sites against the databases, website holds the Rocket API and the server logic, reports is a shared protocol for submitting reports, reporter is the Cheburcheck Reporter, and probe is the Cheburcheck Probe. The compose file backs that up with a long row of PROBE_ settings, including PROBE_CONFIG_PATH, PROBE_RATE_LIMIT_RPM with a default of 5, and PROBE_RESPONSE_CACHE_SECONDS with a default of 10800.

One default deserves a second look:

yaml
      PROBE_UPDATE_PUBLIC_BASE_URL: "${PROBE_UPDATE_PUBLIC_BASE_URL:-https://cheburcheck.ru/api/v1/probe-updates}"
      PROBE_UPDATE_CACHE_SECONDS: "${PROBE_UPDATE_CACHE_SECONDS:-300}"

That variable already points at the public instance, so a local deployment advertises the public update endpoint unless you override it. Check it before you trust local probe data. The rate limit of 5 requests per minute is a sensible guard for a probe that talks to remote hosts, and it also means a local instance cannot be used to sweep a large domain list quickly.

Checking a downloaded list by hand is the real alternative

The list sources the project uses are published and reachable on their own, so the obvious alternative is to fetch the raw files and search them yourself. The difference in approach is concrete rather than cosmetic. Working from a downloaded file means writing your own range handling to match a network block, and it means matching the text you typed rather than the address the name currently resolves to, so you lose the resolution step that makes the Rocket backend interesting.

Cheburcheck moves that work into a Rust service: Quad9 resolution first, prefix trees for the match, cached databases with a defined refresh interval, a JSON API you can call from a script, and a public form, a probe and a reporter layered on top. If you only need to look up a handful of names once, a direct download has fewer moving parts and no database to run. If you need a scheduled answer from a script, the API and the cache behaviour are the parts worth adopting, and the BSD-3-Clause code lets you run it internally on infrastructure you control. The probe and reporter are the pieces with no real substitute in the manual approach.

Two independent release trains and an undocumented upgrade path

The licence is BSD-3-Clause, so modifying and redistributing the code is permitted under its terms, and there is no copyleft obligation to worry about when you embed parts of it. That is the licence side of the picture, without any interpretation of what your use case requires.

The project is not archived and the last push was on 2026-10-19. The release history shows two separate trains: probe-v0.8.2 on 2026-10-19, and website-v1.5.2 and website-v1.5.1 both on 2026-10-17. The versions move independently, so the frontend service and the backend service are not released as one artifact, and a version bump in one does not imply anything about the other.

What is not written down matters for an operator. There is no documented rollback procedure, no migration guide for the database schema behind DATABASE_URL and CLICKHOUSE_URL, no pinned set of upstream list versions, and no install path outside Docker. There is also no API reference beyond the route prefixes, so a client author is left reading the source. If you need a promise about list freshness or a support commitment, the project does not offer one, and the people who will be happiest with it are those who already operate networks in Russia and can maintain the deployment themselves.

Editorial conclusion

Adopt Cheburcheck if you want a self hosted answer to a single question, whether a specific domain or IP is currently listed as blocked, and you can accept an answer that is only as fresh as your cache download on that host. Do not adopt it as a way to reach blocked content, as a general purpose corporate filtering product, or as a source you will cite in a compliance report without checking the freshness of the databases yourself. Before deploying, verify that your host can reach the three listed sources, that DATABASE_URL and CLICKHOUSE_URL point at real instances, and that PROBE_UPDATE_PUBLIC_BASE_URL is not left pointing at the public service.

Frequently asked questions

How do I run Cheburcheck locally?

The documented command is docker compose up --build, which starts nginx, the SvelteKit frontend, the Rocket backend and the rmqtt broker. By default nginx listens on http://localhost:8080, and the host port can be changed with HTTP_PORT.

Which block lists does Cheburcheck check against?

The listed sources are 123jjck/cdn-ip-ranges, antifilter.download and antifilter.network, on top of Roskomnadzor registry data, and the project also builds its own allow lists with the Cheburcheck Reporter. Matches against the network ranges are done with prefix trees.

How often does Cheburcheck refresh its verification databases?

Databases are cached in database-cache/, with the path set by DATABASE_CACHE_DIR, the main refresh interval by DATABASE_INTERVAL_SECONDS, and the retry after a failed download by DATABASE_RETRY_INTERVAL_SECONDS, which defaults to 300 seconds. A failed download leaves the cached data in place.

Does Cheburcheck help me access a blocked site?

The documented flow resolves the name over DNS over HTTPS, checks it against the block lists, and returns the result through the API. Nothing described there routes or tunnels your traffic, so it reports on blocks rather than circumventing them.

Official sources

  1. License: BSD-3-Clause
  2. LowderPlay/cheburcheck on GitHub
  3. Project website
  4. README
  5. Releases
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/lowderplay-cheburcheck.svg)](https://hysenlabs.com/projects/lowderplay-cheburcheck)

Was this useful?