DEV Community

Cover image for How I Built a Real-Time Domain Block Checker for Indonesian ISPs (TrustPositif)
Tomy Purnama
Tomy Purnama

Posted on

How I Built a Real-Time Domain Block Checker for Indonesian ISPs (TrustPositif)

How I Built a Real-Time Domain Block Checker for Indonesian ISPs

If you run a website targeting Indonesian users, you've probably heard of TrustPositif — the government's domain blocking system managed by Komdigi (Ministry of Communication). Over 1 million domains are blocked, and ISPs like IndiHome, Telkomsel, Biznet, First Media, and MyRepublic are legally required to enforce it.

The worst part? Your domain can get blocked without any notification.

I built SafeDomain to solve this — a free tool that checks your domain against TrustPositif AND 5 major ISPs simultaneously, in real-time.

The Problem

Indonesian ISPs block domains at the DNS level. When a domain is flagged:

  1. User's browser sends DNS request to ISP resolver
  2. ISP checks against TrustPositif database
  3. If blocked → redirected to ISP's block page
  4. If not → domain loads normally

The tricky part is that each ISP has different DNS resolvers and sometimes blocks domains at different times. IndiHome might block a domain before Telkomsel does.

The Tech Stack

  • Flask — lightweight Python web framework
  • SQLite — caching layer (WAL mode for concurrent reads)
  • dnspython — DNS resolution per ISP
  • requests — TrustPositif API calls
  • Gunicorn — WSGI server behind Nginx
  • Cloudflare — CDN + DDoS protection

How the Domain Check Works

import dns.resolver
import requests

ISP_RESOLVERS = {
    "IndiHome": "118.98.44.10",
    "Telkomsel": "198.0.0.1", 
    "Biznet": "180.131.144.144",
    "First Media": "103.12.160.2",
    "MyRepublic": "202.152.2.2",
}

INDIHOME_BLOCK_IP = "36.86.63.182"  # IndiHome's block redirect IP

def check_isp_block(domain: str) -> dict:
    results = {}
    for isp, resolver_ip in ISP_RESOLVERS.items():
        try:
            resolver = dns.resolver.Resolver()
            resolver.nameservers = [resolver_ip]
            resolver.lifetime = 3.0
            answers = resolver.resolve(domain, 'A')
            resolved_ip = str(answers[0])
            results[isp] = "BLOCKED" if resolved_ip == INDIHOME_BLOCK_IP else "SAFE"
        except dns.exception.DNSException:
            results[isp] = "ERROR"
    return results

def check_trustpositif(domain: str) -> str:
    try:
        resp = requests.get(
            f"https://trustpositif.komdigi.go.id/api/check?domain={domain}",
            timeout=5
        )
        data = resp.json()
        return "BLOCKED" if data.get("status") == "blocked" else "SAFE"
    except:
        return "ERROR"
Enter fullscreen mode Exit fullscreen mode

Caching Strategy

Running live DNS checks on every request would be slow. I implemented a SQLite cache with 1-hour TTL:

import sqlite3
from datetime import datetime, timedelta

def get_cache(domain: str):
    conn = sqlite3.connect("cache.db")
    row = conn.execute(
        "SELECT status, checked_at, detail FROM cache WHERE domain = ?", 
        (domain,)
    ).fetchone()
    conn.close()

    if row:
        checked_at = datetime.strptime(row[1], "%Y-%m-%d %H:%M:%S")
        age = (datetime.now() - checked_at).total_seconds()
        if age < 3600:  # 1 hour TTL
            return row
    return None

def set_cache(domain: str, status: str, detail: str = ""):
    conn = sqlite3.connect("cache.db")
    conn.execute(
        """INSERT OR REPLACE INTO cache (domain, status, checked_at, detail) 
           VALUES (?, ?, datetime('now'), ?)""",
        (domain, status, detail)
    )
    conn.commit()
    conn.close()
Enter fullscreen mode Exit fullscreen mode

This brought response time from ~2s to ~150ms for cached domains.

Nginx Microcache Layer

On top of SQLite caching, I added Nginx proxy caching for 10 minutes:

proxy_cache_path /tmp/nginx_cache levels=1:2 
  keys_zone=cek_cache:10m max_size=100m 
  inactive=10m use_temp_path=off;

location ~* ^/cek/ {
    proxy_cache cek_cache;
    proxy_cache_valid 200 10m;
    proxy_cache_use_stale error timeout updating;
    proxy_pass http://127.0.0.1:5003;
}
Enter fullscreen mode Exit fullscreen mode

Result: 0.65s → 0.15s on repeated requests.

SEO for a Domain Checker Tool

Since the tool checks 300+ popular Indonesian domains, each /cek/[domain] page is indexed by Google with dynamic meta tags:

@app.route("/cek/<path:domain>")
def cek(domain):
    # Dynamic meta tags per domain
    return render_template("cek.html", 
        domain=domain,
        title=f"Cek {domain} — Diblokir TrustPositif?",
        description=f"Cek apakah {domain} diblokir TrustPositif Komdigi dan 5 ISP Indonesia."
    )
Enter fullscreen mode Exit fullscreen mode

Combined with FAQ JSON-LD schema, each domain page becomes a potential long-tail SEO target.

Lessons Learned

1. ISP DNS resolvers are flaky
Some Indonesian ISP resolvers have high latency or drop packets. Always set a short timeout (3s) and handle errors gracefully.

2. TrustPositif API is rate-limited
The official API can be slow. Cache aggressively.

3. Domain blocking is asynchronous across ISPs
IndiHome might block a domain hours before Telkomsel. Always check all ISPs independently.

4. Users want simplicity
The most popular feature is the simple "Is it blocked or not?" indicator, not the detailed per-ISP breakdown.

What's Next

  • Telegram bot for automated monitoring with instant notifications
  • SmartRedirector — auto-rotate to backup domains when primary is blocked
  • API endpoint for developers to integrate into their own monitoring

Try It

🔍 Free domain checker: cek.opensite.site

📊 Full monitoring dashboard: safedomain.id


Built with Flask, SQLite, and a lot of frustration with Indonesian internet censorship 😅

Have you dealt with domain blocking in your country? I'd love to hear how others handle this problem.

Top comments (0)