SSD Nodes Learn 🎉 VPS $5.50/माह से
गाइड Matt Connorलेखक: Matt Connor

VPS पर Headless Browser कैसे चलाएं: पूरी गाइड

VPS पर Headless Chromium चलाते समय /dev/shm की कमी, सैंडबॉक्स एरर और फोंट की समस्याओं से बचें। अपने AI एजेंट के लिए सही रिसोर्स लिमिट सेट करने का तरीका यहाँ विस्तार से जानें।

आप क्या चला रहे हैं

VPS पर headless browser एक ऐसा Chromium है जिसमें कोई window नहीं होती, और इसे किसी व्यक्ति के बजाय आपके code द्वारा संचालित किया जाता है। सर्वर पर यह एक long-lived process tree है जिससे आपका agent local socket के माध्यम से बात करता है। इसे install करने के लिए केवल एक command की आवश्यकता होती है। असली काम उसके बाद शुरू होता है। आप browser द्वारा machine से लिए जाने वाले संसाधनों को सीमित करते हैं और इसके control endpoint को public internet से दूर रखते हैं।

यह guide मानती है कि आपने tool चुन लिया है और अब आपको इसे operate करना है। यदि आप अभी भी crawlers और extractors की तुलना कर रहे हैं, तो self-hosted Firecrawl के विकल्पों से शुरुआत करें और फिर वापस आएँ। नीचे दी गई हर चीज़ Playwright के Chromium का उपयोग करती है, क्योंकि Playwright अपना browser build और dependency installer स्वयं प्रदान करता है, इसलिए वही commands bare Ubuntu VPS और container के अंदर समान रूप से काम करते हैं। अगस्त 2026 तक के अनुसार ये version वर्तमान हैं।

Install Chromium without guessing at dependencies

npm i -D playwright@1.62.0
npx playwright install --with-deps chromium

--with-deps runs apt for the shared libraries and fonts Chromium needs, and asks for root when it gets there. The browser build itself downloads into ~/.cache/ms-playwright for the user who ran the command. That matters on a server, because the service user is usually not the user you log in as. Install the system packages once as an admin with sudo npx playwright install-deps chromium, then set PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers in both the install command and the service unit so one copy is shared. A service that cannot see its browser fails at launch with a message naming the path it searched.

Pin the Playwright version. Each release is tied to one browser build, so an unpinned npm update can swap the browser under a running service. Playwright 1.62 is current as of August 2026.

Two Chromium builds exist and they are not the same program. The default download is the headless shell, a smaller binary that only runs headless, and npx playwright install --with-deps --only-shell installs that alone. The full browser is what you get with the chromium channel, which Playwright's browser docs call "the real Chrome browser, and is thus more authentic, reliable, and offers more features". Use the shell for bulk fetching. Use the full browser when a site behaves differently and you need to find out why.

कंटेनर में headless browser क्रैश क्यों होता है

Docker प्रत्येक कंटेनर को 64 MB का /dev/shm देता है। Docker का documentation स्पष्ट है: "यदि आप आकार को पूरी तरह से छोड़ देते हैं, तो सिस्टम 64m का उपयोग करता है"। Chromium रेंडर की गई सामग्री को अपनी प्रक्रियाओं के बीच उस साझा मेमोरी क्षेत्र के माध्यम से भेजता है, इसलिए एक भारी पेज उसे भर सकता है। इसके बाद रेंडरर बंद हो जाता है और आपका क्लाइंट एक क्रैश हुए टारगेट की रिपोर्ट करता है, जबकि वही पेज आपके लैपटॉप पर ठीक काम करता है। कुछ भी बदलने से पहले कंटेनर के अंदर से आकार की पुष्टि करें।

df -h /dev/shm

इसके दो वास्तविक समाधान हैं और वे विकल्प हैं, न कि एक साथ उपयोग करने वाली चीजें। --ipc=host कंटेनर को होस्ट IPC नेमस्पेस में डाल देता है ताकि वह होस्ट के /dev/shm का उपयोग करे, जो सामान्यतः RAM का आधा होता है। Playwright का Docker गाइड इसकी अनुशंसा करता है, क्योंकि इसके बिना "Chromium की मेमोरी खत्म हो सकती है और वह क्रैश हो सकता है"। इसकी कीमत यह है कि आप कंटेनर और होस्ट के बीच IPC आइसोलेशन खो देते हैं। --shm-size=1g निजी नेमस्पेस को बनाए रखता है और बस माउंट को बड़ा कर देता है।

docker run --rm -it --init --ipc=host --user pwuser mcr.microsoft.com/playwright:v1.62.0-noble /bin/bash

फ्लैग --disable-dev-shm-usage वह उत्तर है जो आपको अधिकांश खोज परिणामों में मिलेगा, और यह कुछ अलग करता है: यह उन फाइलों को /dev/shm से हटाकर एक अस्थायी निर्देशिका में ले जाता है। यदि /tmp डिस्क पर स्थित है, तो आपने क्रैश के बदले धीमी रेंडरिंग और डिस्क राइट्स का सौदा किया है। यदि /tmp एक tmpfs है, तो डेटा बिना किसी आकार सीमा के वापस RAM में आ जाता है, जो एक तरीका है जिससे ब्राउज़र एक छोटे VPS की मेमोरी खत्म कर देता है। इसके बजाय /dev/shm का आकार उचित रूप से निर्धारित करें।

--no-sandbox का वास्तविक मूल्य

Chromium हर renderer को Linux user namespaces पर आधारित एक sandbox में अलग रखता है। वह sandbox एक hostile page और आपके सर्वर के बीच की सीमा है। जब यह शुरू नहीं हो पाता, तो Chromium चलने से मना कर देता है, और log में इस तरह की एक line होती है:

Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted

सामान्य सलाह --no-sandbox का उपयोग करना है। Chromium का अपना security documentation इसकी कीमत के बारे में स्पष्ट है: यह flag "Chromium के महत्वपूर्ण security features को disable कर देता है और open web पर browsing करते समय इसका उपयोग कभी नहीं किया जाना चाहिए"। परिभाषा के अनुसार, links का अनुसरण करने वाला एक agent open web पर ही browse कर रहा होता है। वास्तविक कारण का पता लगाएँ।

दो कारण लगभग हर मामले को कवर करते हैं। Browser को root के रूप में चलाने से sandbox disable हो जाता है, क्योंकि यह उन privileges को नहीं छोड़ सकता जो इसके पास पहले से हैं, यही कारण है कि Playwright की image में pwuser नामक एक सामान्य user होता है। Ubuntu 24.04 और उसके बाद के संस्करणों पर, AppArmor unprivileged user namespaces को प्रतिबंधित करता है, और ऐसे path पर स्थित Chromium binary जिसे कोई भी shipped profile कवर नहीं करती, उसे denied कर दिया जाता है। ~/.cache/ms-playwright के अंतर्गत Playwright का download बिल्कुल ऐसा ही एक path है। दोनों की जाँच करें:

id -u
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo dmesg | grep -i userns_create

sysctl से प्राप्त 1 और apparmor="DENIED" operation="userns_create" युक्त एक kernel line दूसरे कारण की पुष्टि करती है। /etc/apparmor.d/pw-chromium में उस एक binary को अनुमति दें, जो box पर बाकी सब कुछ के लिए प्रतिबंध को लागू रखता है:

abi <abi/4.0>,
include <tunables/global>

profile pw-chromium /home/*/.cache/ms-playwright/*/chrome-linux/{chrome,headless_shell} flags=(unconfined) {
  userns,
}

इसे sudo apparmor_parser -r /etc/apparmor.d/pw-chromium के साथ load करें। Path में browser revision होता है, इसलिए यह हर Playwright upgrade पर बदल जाता है। ऊपर दिए गए globs उसके बाद भी काम करते रहते हैं। एक सटीक path के विरुद्ध लिखी गई profile चुपचाप match करना बंद कर देती है, और एक ऐसे update के बाद जो असंबंधित लगता है, browser फिर से fail होने लगता है।

स्क्रीनशॉट खाली या बॉक्स से भरे हुए क्यों आते हैं

एक खाली स्क्रीनशॉट, या खाली आयतों (rectangles) से भरा हुआ स्क्रीनशॉट, आमतौर पर रेंडरिंग बग के बजाय एक फॉन्ट की समस्या होती है। install-deps एक वर्किंग बेस खींचता है: fonts-liberation, fonts-freefont-ttf, fonts-noto-color-emoji, fonts-unifont, जापानी के लिए fonts-ipafont-gothic, चीनी के लिए fonts-wqy-zenhei, थाई के लिए fonts-tlwg-loma-otf। उस सेट में Noto CJK नहीं है, इसलिए कोरियन और कई अन्य लिपियाँ उस फॉन्ट पर वापस चली जाती हैं जिसे fontconfig ढूंढ पाता है। अनुमान लगाने के बजाय fontconfig से पूछें:

fc-match "sans-serif:lang=ko"
fc-match "sans-serif:lang=ar"
fc-list | wc -l

यदि कोई भाषा जिसमें आपकी रुचि है, वह unifont या बिना वास्तविक ग्लिफ़ (glyphs) वाले फॉलबैक पर रिज़ॉल्व होती है, तो fonts-noto-core और fonts-noto-cjk इंस्टॉल करें, फिर जांच को दोबारा चलाएं। Fontconfig अपने परिणामों को कैश (cache) करता है, इसलिए फॉन्ट इंस्टॉल करने के बाद ब्राउज़र को रीस्टार्ट करें। बिना किसी फॉन्ट वाली एक स्ट्रिप्ड इमेज स्टार्टअप पर Fontconfig error: Cannot load default config file लॉग करती है और हर पेज को खाली रेंडर करती है।

लोकेल (locale) और टाइम ज़ोन फॉन्ट से अलग होते हैं, और वे यह बदलते हैं कि पेज पर क्या लिखा है, न कि केवल यह कि वह कैसा दिखता है। एक कंटेनर में सामान्यतः LANG अनसेट होता है और TZ UTC पर होता है, इसलिए साइटें अंग्रेजी में जानकारी देती हैं और UTC टाइमस्टैम्प प्रिंट करती हैं, और आपका एजेंट ऐसा समय रिपोर्ट करता है जो उस देश के व्यक्ति द्वारा देखे जाने वाले समय से मेल नहीं खाता। इन्हें प्रति मशीन के बजाय प्रति ब्राउज़र कॉन्टेक्स्ट सेट करें, ताकि एक ब्राउज़र अलग-अलग क्षेत्रों के कार्यों को पूरा कर सके।

const context = await browser.newContext({
  locale: 'en-GB',
  timezoneId: 'Europe/Paris',
});

लीक हुए ब्राउज़र प्रोसेस सर्वर को स्वैप (swap) पर क्यों मजबूर करते हैं

"zombie" नाम की दो अलग-अलग समस्याएँ होती हैं। एक वास्तविक zombie वह प्रोसेस है जो समाप्त हो चुका है लेकिन उसके पैरेंट ने कभी wait() कॉल नहीं किया। यह केवल एक PID एंट्री रखता है और कुछ नहीं, इसलिए यह मेमोरी का उपयोग नहीं करता है। जब ब्राउज़र किसी कंटेनर में PID 1 के रूप में चलता है, तो आप इन्हें इकट्ठा करते हैं, क्योंकि PID 1 में कोई डिफ़ॉल्ट रीपर (reaper) नहीं होता है। Docker का --init फ्लैग ठीक इसी समस्या को हल करता है, क्योंकि यह एक छोटा init चलाता है जो "सिग्नल्स को फॉरवर्ड करता है और प्रोसेस को रीप (reap) करता है"। Compose में यही काम init: true करता है।

वह लीक जो वास्तव में आपके सर्वर को स्वैप पर मजबूर करता है, वह अलग है: ये लाइव Chromium प्रोसेस हैं जिन्हें किसी ने बंद नहीं किया। यह तब होता है जब कोई टास्क newContext() और close() के बीच विफल हो जाता है, या जब कंट्रोलिंग स्क्रिप्ट को मार दिया जाता है और वह अपने ब्राउज़र ट्री को अनाथ (orphaned) छोड़ देती है। इसका सबसे खराब रूप वह कोड है जो हर रिक्वेस्ट के लिए एक नया ब्राउज़र लॉन्च करता है। उन्हें गिनें:

pgrep -c -f 'headless_shell|chrome'
ps -eo pid,ppid,rss,etime,comm --sort=-rss | head -20

टास्क के बीच वह संख्या अपने आइडल (idle) मान पर वापस आ जानी चाहिए। यदि यह एक दिन में बढ़ती है, तो सुधार आपके कोड में है, न कि लॉन्च फ्लैग्स में: कॉन्टेक्स्ट को finally ब्लॉक में बंद करें, SIGTERM पर ब्राउज़र को बंद करें, और एक महीने तक चलाने के बजाय एक निश्चित संख्या में टास्क के बाद ब्राउज़र को रीसायकल करें। systemd के तहत, स्टॉप या रीस्टार्ट यूनिट के cgroup में मौजूद हर चीज़ को मार देता है, इसलिए sudo systemctl restart browser.service एक विश्वसनीय रीसेट है। टर्मिनल मल्टीप्लेक्सर के अंदर हाथ से शुरू किए गए ब्राउज़र की ऐसी कोई गारंटी नहीं होती है, और उसके अनाथ प्रोसेस सेशन के खत्म होने के बाद भी चलते रहते हैं।

एक ब्राउज़र कॉन्टेक्स्ट को कितनी RAM की आवश्यकता होती है

यह प्रश्न सटीक रूप से पूछें, क्योंकि "एक ब्राउज़र" का अर्थ एक प्रोसेस नहीं होता है। Chromium एक ब्राउज़र प्रोसेस, एक GPU प्रोसेस, यूटिलिटी प्रोसेसेस और प्रति साइट एक रेंडरर प्रोसेस चलाता है, और साइट आइसोलेशन के कारण क्रॉस-साइट iframes को भी अपना अलग रेंडरर मिलता है। एक BrowserContext उसी ट्री के भीतर एक अलग कुकी जार और स्टोरेज एरिया होता है, इसलिए दूसरा कॉन्टेक्स्ट सस्ता होता है। दूसरा पेज सस्ता नहीं होता, क्योंकि यह रेंडरर प्रोसेसेस को शुरू करता है, और विज्ञापनों से भरा पेज कई प्रोसेसेस शुरू कर देता है।

इसलिए मापने योग्य संख्या आपके अपने वर्कलोड के तहत पूरे ट्री के लिए पीक मेमोरी है। किसी और के ब्लॉग से लिया गया आंकड़ा यहाँ बेकार है, क्योंकि आपका एजेंट जिन पेजों को खोलता है, वही उत्तर तय करते हैं। उस मशीन पर मापें जिसका आप उपयोग करेंगे, उन साइटों के विरुद्ध जिन पर आप जाएंगे:

sudo systemd-run --unit=browser-probe -p MemoryMax=2G -p MemorySwapMax=0 -p WorkingDirectory=/srv/agent /usr/bin/node worker.js
systemctl status browser-probe

Ubuntu 24.04 पर उस आउटपुट में Memory: लाइन यूनिट के लिए वर्तमान और पीक उपयोग दोनों की रिपोर्ट करती है। वर्कर को एक बार में एक पेज के साथ चलाएं, पीक नोट करें, फिर यह देखने के लिए कि दूसरे पेज की वास्तविक लागत क्या है, दो पेज खोलकर दोहराएं। इसके बाद कॉन्करेंसी एक गणितीय गणना है: कुल RAM लें, बाकी बॉक्स को जितनी आवश्यकता है उसे घटाएं, कुछ सौ MB का हेडरूम रखें, और प्रति वर्कर मापी गई पीक से विभाजित करें। इसके नीचे की मशीन के साइजिंग के लिए, एजेंट VPS को कितनी RAM और CPU की आवश्यकता है देखें।

उस संख्या को दो स्थानों पर लागू करें। अपने कोड में, एक फिक्स्ड वर्कर पूल या सेमाफोर का उपयोग करें, ताकि एजेंट अनुरोधों का एक समूह ब्राउज़र लॉन्च करने के बजाय कतार में लग जाए। OS में, एक cgroup लिमिट का उपयोग करें, ताकि कतार में कोई बग पूरी मशीन को डाउन न कर सके:

[Service]
MemoryMax=2G
MemorySwapMax=0
TasksMax=512
Restart=always

MemorySwapMax=0 जितना दिखता है उससे कहीं अधिक मायने रखता है। इसके बिना, जब cgroup लिमिट तक पहुँचता है तो वह पेजों को स्वैप में धकेल देता है, जिससे बॉक्स चालू तो रहता है लेकिन हर अनुरोध धीमा हो जाता है, जिसे स्पष्ट विफलता की तुलना में डायग्नोस करना कठिन होता है। इसके साथ, कर्नल उस cgroup के भीतर ब्राउज़र ट्री को समाप्त कर देता है, systemd यूनिट को रीस्टार्ट करता है, और sshd सुरक्षित रहता है। Compose में यही कंट्रोल्स mem_limit, shm_size और init हैं, जिन्हें Docker Compose में मेमोरी लिमिट सेट करना में कवर किया गया है।

ब्राउज़र एंडपॉइंट को पब्लिक इंटरनेट से दूर रखें

Playwright ब्राउज़र को सर्वर के रूप में चला सकता है और आपके एजेंट को एक WebSocket URL प्रदान कर सकता है:

const { chromium } = require('playwright');
const server = await chromium.launchServer({ port: 3000 });
console.log(server.wsEndpoint());

उस एंडपॉइंट पर कोई लॉगिन नहीं होता है। Playwright का API डॉक्यूमेंटेशन इसे स्पष्ट रूप से बताता है: "कोई भी प्रोसेस या वेब पेज (Playwright में चलने वाले सहित) जिसे wsPath की जानकारी है, वह OS यूजर का नियंत्रण ले सकता है।" डिफ़ॉल्ट होस्ट localhost है, जो "केवल लूपबैक इंटरफ़ेस से कनेक्शन स्वीकार करता है", और डॉक्यूमेंटेशन चेतावनी देता है कि 0.0.0.0 जैसा स्पष्ट पता देने से "ब्राउज़र RPC उस किसी भी चीज़ के लिए खुल जाता है जो लिसनिंग पोर्ट तक पहुँच सकती है"। Chrome का अपना --remote-debugging-port और भी खराब है। DevTools प्रोटोकॉल में किसी भी प्रकार का प्रमाणीकरण नहीं होता है और यह पूरी तरह से लूपबैक पर बाइंड होने पर निर्भर करता है।

जाँचें कि आपने वास्तव में क्या पब्लिश किया है, और इसे VPS के साथ-साथ किसी दूसरे मशीन से भी चेक करें:

ss -ltnp

0.0.0.0 पर बाइंड किया गया ब्राउज़र पोर्ट एक सुरक्षा खामी (finding) है। याद रखें कि अधिकांश प्रदाता अपने कंट्रोल पैनल में एक अलग नेटवर्क फ़ायरवॉल चलाते हैं, जिसके बारे में आपके ufw रूल्स को कोई जानकारी नहीं होती है। एंडपॉइंट तक पहुँचने के लिए SSH टनल या प्राइवेट VPN का उपयोग करें:

ssh -N -L 3000:127.0.0.1:3000 you@your-vps

यहाँ जोखिम केवल ब्राउज़र टाइम चोरी होने से कहीं अधिक है। एक ब्राउज़र जिसे आप नियंत्रित कर सकते हैं, वह आपके नेटवर्क के भीतर बैठा एक रिक्वेस्ट-फोर्जरी (request-forgery) मशीन है। जो कोई भी उस सॉकेट तक पहुँचता है, वह इसे http://127.0.0.1:8080, आपके डेटाबेस एडमिन पेज, या 169.254.169.254 पर क्लाउड मेटाडेटा एड्रेस को फेच करने के लिए कह सकता है, और फिर पेज से रिस्पॉन्स पढ़ सकता है। आपका फ़ायरवॉल देखता है कि रिक्वेस्ट स्वयं VPS से आ रही है, जिसे अनुमति प्राप्त है। कंट्रोल एंडपॉइंट को उस बॉक्स पर शेल एक्सेस के समान ही मानें।

MCP सर्वर का स्वरूप भी ऐसा ही है। npx @playwright/mcp@latest --headless --port 8931 लोकलहोस्ट पर HTTP के माध्यम से सर्व करता है, और --host 0.0.0.0 वह फ्लैग है जो एक लोकल टूल को पब्लिक बना देता है। प्रोजेक्ट का README स्पष्ट रूप से कहता है कि Playwright MCP "एक सुरक्षा सीमा (security boundary) नहीं है"। पोर्ट को लूपबैक पर रखें और एजेंट को उसी टनल के माध्यम से उस तक पहुँचने दें।

आपके एजेंट द्वारा पढ़ी जाने वाली पृष्ठ सामग्री अविश्वसनीय इनपुट है

जो एजेंट ओपन वेब ब्राउज़ करता है, वह अजनबियों द्वारा लिखे गए टेक्स्ट को एक ऐसे मॉडल में फीड करता है जिसमें आपके निर्देश भी होते हैं। एक वेब पेज में उस मॉडल को संबोधित टेक्स्ट हो सकता है, जो उसे कार्य छोड़ने, टूल कॉल करने या किसी URL पर डेटा पोस्ट करने के लिए कह सकता है। मॉडल दोनों को टेक्स्ट के रूप में प्राप्त करता है, इसलिए उसके पास पेज के शब्दों और आपके निर्देशों के बीच अंतर करने का कोई विश्वसनीय तरीका नहीं है। सेटअप को इस तरह डिज़ाइन करें कि किसी हानिकारक पेज के पास करने के लिए बहुत कम अवसर हों।

  • ब्राउज़र को उसके अपने OS यूजर के अंतर्गत चलाएं, जिसके एनवायरनमेंट में कोई SSH keys और कोई cloud credentials न हों।
  • प्रत्येक कार्य के लिए एक नए कॉन्टेक्स्ट का उपयोग करें, और Playwright MCP के साथ --isolated का उपयोग करें, ताकि एक साइट का सेशन अगले पेज के लिए उपलब्ध न रहे।
  • जहाँ कार्य अनुमति दे, वहाँ एक origin allowlist रखें। Playwright MCP --allowed-origins और --blocked-origins को सेमीकोलन-सेपरेटेड लिस्ट के रूप में लेता है।
  • किसी भी ऐसी क्रिया से पहले मानवीय हस्तक्षेप अनिवार्य करें जो स्थिति (state) को बदलती हो, जैसे ईमेल भेजना या पैसे खर्च करना।

इससे भी बेहतर यह है कि पूरे ब्राउज़र को एक ऐसी मशीन पर रखें जिसे आप नष्ट करके दोबारा बना सकें, जो डिस्पोजेबल VM में कोडिंग एजेंट चलाने के समान ही तर्क है। यदि एजेंट का वास्तविक कार्य ओपन-एंडेड ब्राउज़िंग के बजाय सर्च करना है, तो पूर्ण ब्राउज़र की तुलना में एक सीमित टूल अधिक सुरक्षित है: आपके अपने SearXNG द्वारा समर्थित सर्च स्किल हानिकारक पेज को लोड किए बिना ही परिणाम प्रदान करती है।

FAQ

Chromium Docker में क्रैश क्यों हो जाता है जबकि उसी VPS पर सीधे चलाने पर ठीक काम करता है?

क्योंकि container को डिफ़ॉल्ट रूप से 64 MB का /dev/shm मिलता है, जबकि host के पास यह काफी अधिक होता है। Chromium रेंडर की गई सामग्री को उस shared memory क्षेत्र के माध्यम से भेजता है, इसलिए एक भारी पेज उसे भर देता है और रेंडरर बंद हो जाता है। पुष्टि करने के लिए container के अंदर df -h /dev/shm चलाएँ, फिर इसे या तो --ipc=host के साथ शुरू करें, जो host की shared memory का उपयोग करता है, या --shm-size=1g के साथ, जो container की अपनी memory को बढ़ा देता है। --disable-dev-shm-usage समस्या को केवल /tmp पर स्थानांतरित करता है।

क्या --no-sandbox सुरक्षित है यदि VPS पर कुछ और न चल रहा हो?

नहीं। Sandbox ही वह चीज़ है जो किसी दुर्भावनापूर्ण पेज को मशीन के बाकी हिस्सों तक पहुँचने से रोकती है, और Chromium का documentation कहता है कि यह flag "Chromium की महत्वपूर्ण सुरक्षा सुविधाओं को अक्षम करता है और open web ब्राउज़ करते समय इसका उपयोग कभी नहीं किया जाना चाहिए"। लिंक का अनुसरण करने वाला एक agent open web ही ब्राउज़ कर रहा होता है। इसके बजाय मूल कारण को ठीक करें: ब्राउज़र को root के रूप में न चलाएँ, और Ubuntu 24.04 पर ब्राउज़र बाइनरी पाथ के लिए userns, वाला एक AppArmor profile जोड़ें, ताकि उस एक प्रोग्राम के लिए unprivileged user namespaces की अनुमति मिल सके।

मैं एक छोटे VPS पर कितने ब्राउज़र चला सकता हूँ?

इसे मापें, किसी संख्या की नकल न करें। Chromium प्रति साइट एक रेंडरर प्रक्रिया शुरू करता है, इसलिए उत्तर आपके द्वारा खोले गए पेजों पर निर्भर करता है। MemoryMax सेट करके systemd-run के तहत एक worker चलाएँ, systemctl status में Memory: लाइन से peak मान पढ़ें, फिर अपनी खाली RAM को उस peak से विभाजित करें और कुछ headroom बनाए रखें। परिणाम को दो बार लागू करें, अपने कोड में एक queue के साथ और unit file में एक MemoryMax के साथ, ताकि अनुरोधों का अचानक बढ़ना मशीन को swap करने के बजाय प्रतीक्षा करे।

क्या मेरा agent किसी दूसरी मशीन से ब्राउज़र से कनेक्ट हो सकता है?

हाँ, लेकिन port को कभी भी 0.0.0.0 पर bind न करें। Playwright सर्वर endpoint और Chrome DevTools port दोनों ही किसी भी ऐसे client को स्वीकार करते हैं जो उन तक पहुँच सकता है, बिना किसी पासवर्ड के। Listener को 127.0.0.1 पर रखें और कनेक्शन को SSH tunnel या private VPN के माध्यम से ले जाएँ। सर्वर पर ss -ltnp के साथ पुष्टि करें और बाहर से port check करें, साथ ही अपने प्रदाता के अलग network firewall को भी देखें।

पेज लोड होने के बावजूद मेरे स्क्रीनशॉट खाली क्यों हैं?

Fonts की कमी। पेज की स्क्रिप्ट को कवर करने वाली कोई font न होने पर, टेक्स्ट खाली बक्सों के रूप में या बिल्कुल नहीं दिखता है, इसलिए image-light पेज खाली दिखाई देता है। प्रत्येक भाषा जिसे आप scrape करते हैं उसके लिए fc-match "sans-serif:lang=ko" चलाएँ, जब उत्तर एक सामान्य fallback हो तो fonts-noto-core और fonts-noto-cjk इंस्टॉल करें, और ब्राउज़र को पुनरारंभ करें ताकि fontconfig अपना cache पुनः लोड कर सके। बिना किसी font वाले container में स्टार्टअप पर Fontconfig error: Cannot load default config file लॉग होता है।

#headless-browser#playwright#chromium#ai-agents#automation