Screenshot Multiple Domains Directly from Origin, Bypassing Cloudflare
To check that the origin server really renders correctly, rather than viewing what the CDN has cached, I need to force the browser to connect directly to the origin IP — but antivirus software often reverts hosts edits on Windows. A more reliable approach is to override DNS within the browser itself.
I often need to take screenshots of multiple domains — both to check that every site still displays correctly after a server migration and to audit whether the "origin server" really renders correctly, rather than viewing stale content cached by Cloudflare. I started with the usual methods and hit two roadblocks, so I switched to Playwright, which solved both. Here is how I did it.
Roadblock 1: Chrome's screenshot flag is broken
The classic method is to run chrome --headless --screenshot, but since Chrome 132, this flag has been broken with the new headless mode (--headless=new does not support --screenshot). It produces an empty file or an error.
Its behavior changes between Chrome versions to the point that it is unreliable — use Playwright/CDP to control the browser through the protocol instead. It is much more stable across versions.
Roadblock 2: I want to reach the origin, but AV reverts hosts edits
To force a domain to point to the origin IP (bypassing Cloudflare), the usual method is to edit the hosts file. But on Windows, antivirus software (Kaspersky/Defender) often treats hosts edits as malware behavior and immediately reverts them — the changes do not stick.
The solution: Playwright has a process-level DNS override option (--host-resolver-rules) that applies only to the browser instance being run. It does not touch system files, so AV does not see it or revert it.
The script I use
// shot.js — แคปทีละโดเมนจาก sites.txt, บังคับ origin IP ถ้าใส่ --origin
const { chromium } = require('playwright');
const fs = require('fs');
(async () => {
const args = process.argv.slice(2);
const oi = args.indexOf('--origin');
const originIP = oi >= 0 ? args[oi + 1] : null;
const listFile = args[args.length - 1];
const sites = fs.readFileSync(listFile, 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
const launchArgs = [];
if (originIP) {
// ชี้ทุกโฮสต์ไป origin IP — เฉพาะโปรเซสนี้ ไม่แตะ hosts ระบบ
launchArgs.push(`--host-resolver-rules=MAP * ${originIP}`);
}
const browser = await chromium.launch({ args: launchArgs, ignoreHTTPSErrors: true });
for (const url of sites) {
const page = await browser.newPage({ viewport: { width: 1366, height: 900 }, ignoreHTTPSErrors: true });
try {
await page.goto(url.startsWith('http') ? url : 'https://' + url,
{ waitUntil: 'networkidle', timeout: 30000 });
const name = url.replace(/[^a-z0-9]/gi, '_') + '.png';
await page.screenshot({ path: 'shots/' + name, fullPage: false });
console.log('[OK] ' + url);
} catch (e) {
console.log('[ERR] ' + url + ' — ' + e.message);
}
await page.close();
}
await browser.close();
})();
mkdir -p shots
# แคปตามปกติ (ผ่าน Cloudflare)
node shot.js sites.txt
# บังคับชนต้นทาง (ข้าม CDN) — เห็นสิ่งที่ origin เสิร์ฟจริง
node shot.js --origin 203.0.113.10 sites.txt
During a server migration or inspection, I need to know whether the "new origin server" renders correctly before actually switching DNS — MAP * origin_ip makes the browser connect directly to the new server without touching public DNS, so I can see the actual content before cutover.
Things to watch for
- ignoreHTTPSErrors must be enabled when connecting to the origin IP because the destination certificate may not yet match the domain name (a new server with no certificate issued yet) — otherwise, an error occurs before the page loads.
- networkidle helps ensure the page finishes loading before the screenshot, but some sites that send requests continuously will time out — switch to
domcontentloaded+waitForTimeoutif that happens. - For large batches of screenshots, open one page at a time and close it afterward. Do not leave them all open at once, or RAM usage will balloon.
The problem with taking screenshots in bulk comes down to the old tools (a broken chrome flag and hosts edits that AV reverts), rather than the browser itself — once I switched to controlling it through Playwright, both process-level DNS overrides and screenshots became stable across versions, especially for verifying the origin before cutover without touching public DNS. If you handle migrations or audit multiple websites, feel free to adapt this to your work. If I have left anything out or made any typos, I will come back and fix them later.
Want us to set this up?
Get help with server setup, configuration, migration, or troubleshooting. Save time for the work that matters.
Hit something like this? Stuck somewhere?
Come trade notes on the LinuxThai board



Comments 0
No comments yet — be the first