تشغيل متصفح بلا واجهة لوكلاء الذكاء الاصطناعي على VPS
تعرّف إلى أسباب تعطل Chromium بلا واجهة على VPS، من صغر /dev/shm وخيارات sandbox إلى الخطوط والعمليات المتسربة، واضبط الحدود قبل وصول وكيلك إليها.
ما الذي تشغّله
المتصفح عديم الواجهة على VPS هو Chromium من دون نافذة، وتتحكم فيه شيفرتك بدلاً من شخص. على الخادم، يكون المتصفح شجرة عمليات طويلة التشغيل، ويتصل بها وكيلك عبر مقبس محلي. يتطلب تثبيته أمراً واحداً. أما العمل الفعلي فيبدأ بعد ذلك. تضع حداً للموارد التي يمكن للمتصفح استهلاكها من الجهاز، وتُبقي نقطة التحكم الخاصة به خارج الإنترنت العام.
يفترض هذا الدليل أنك حسمت اختيار الأداة، وأن عليك الآن تشغيلها وإدارتها. إذا كنت لا تزال تقارن بين برامج الزحف وأدوات الاستخراج، فابدأ بـبدائل Firecrawl المستضافة ذاتياً ثم عُد إلى هنا. تستخدم جميع الخطوات أدناه Chromium مع Playwright، لأن Playwright يوفّر إصدار المتصفح الخاص به ومثبت التبعيات الخاص به، ولذلك تعمل الأوامر نفسها على VPS يعمل بنظام Ubuntu من دون إعدادات مسبقة وداخل حاوية. الإصدارات محدثة حتى August 2026.
ثبّت Chromium من دون تخمين التبعيات
npm i -D playwright@1.62.0
npx playwright install --with-deps chromiumيشغّل --with-deps الأمر apt لتثبيت المكتبات المشتركة والخطوط التي يحتاج إليها Chromium، ويطلب صلاحيات root عند الوصول إلى هذه الخطوة. يُنزّل إصدار المتصفح نفسه إلى ~/.cache/ms-playwright للمستخدم الذي نفّذ الأمر. وهذا مهم على الخادم، لأن مستخدم الخدمة لا يكون عادةً المستخدم الذي تسجّل الدخول به. ثبّت حزم النظام مرة واحدة بصلاحيات إدارية باستخدام sudo npx playwright install-deps chromium، ثم اضبط PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers في أمر التثبيت ووحدة الخدمة معاً، بحيث تُستخدم نسخة واحدة مشتركة. تفشل الخدمة التي لا يمكنها الوصول إلى المتصفح عند بدء التشغيل، وتعرض رسالة تتضمن المسار الذي بحثت فيه.
ثبّت إصدار Playwright بشكل صريح. يرتبط كل إصدار بإصدار واحد من المتصفح، لذلك قد يؤدي npm update غير المقيّد إلى استبدال المتصفح أثناء تشغيل الخدمة. يُعد Playwright 1.62 الإصدار الحالي في August 2026.
يوجد إصداران من Chromium، وهما ليسا البرنامج نفسه. التنزيل الافتراضي هو headless shell، وهو ملف ثنائي أصغر يعمل في الوضع headless فقط، ويثبّت npx playwright install --with-deps --only-shell هذا الملف وحده. أما المتصفح الكامل فتحصل عليه باستخدام قناة chromium، التي تصفها وثائق متصفح Playwright بأنها «متصفح Chrome الحقيقي، ولذلك فهي أكثر أصالة وموثوقية وتوفّر مزيداً من الميزات». استخدم shell لجلب كميات كبيرة من البيانات. استخدم المتصفح الكامل عندما يتصرف أحد المواقع بطريقة مختلفة وتحتاج إلى معرفة السبب.
سبب تعطل متصفح بلا واجهة رسومية داخل حاوية
يمنح Docker كل حاوية مساحة /dev/shm بحجم 64 MB. توضح وثائق Docker ذلك صراحةً: "إذا حذفت الحجم بالكامل، يستخدم النظام 64m". يمرر Chromium المحتوى المُصيَّر بين عملياته عبر مساحة الذاكرة المشتركة هذه، لذلك قد تملؤها صفحة واحدة كثيفة. عندها تتوقف عملية التصيير، ويبلّغ عميلك عن تعطل الهدف، رغم أن الصفحة تعمل بشكل طبيعي على حاسوبك المحمول. تحقّق من الحجم من داخل الحاوية قبل تغيير أي شيء.
df -h /dev/shmهناك حلّان فعليان، وهما بديلان لا يُستخدمان معاً. يضع --ipc=host الحاوية في مساحة أسماء IPC الخاصة بالمضيف، ولذلك تستخدم /dev/shm الخاصة بالمضيف، والتي تساوي عادةً نصف الذاكرة RAM. يوصي دليل Docker الخاص بـPlaywright بهذا الخيار، لأن "Chromium قد تنفد ذاكرته ويتعطل" بدونه. لكنك بذلك تتخلى عن عزل IPC بين الحاوية والمضيف. يحافظ --shm-size=1g على مساحة أسماء خاصة، ويزيد حجم الـmount فحسب.
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 كل عملية عرض داخل sandbox مبني على Linux user namespaces. يشكّل هذا الـsandbox الحد الفاصل بين الصفحة الضارة وخادمك. عندما يتعذر تشغيله، يرفض Chromium العمل، ويحتوي السجل على سطر مثل الآتي:
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permittedالنصيحة المعتادة هي --no-sandbox. توضح وثائق أمان Chromium صراحةً تكلفة ذلك: إن هذا الخيار «يعطّل ميزات أمان مهمة في Chromium، ويجب عدم استخدامه مطلقاً عند تصفح الويب المفتوح». والوكيل الذي يتبع الروابط يتصفح الويب المفتوح بحكم التعريف. ابحث عن السبب الحقيقي.
يغطي سببان معظم الحالات. يؤدي تشغيل المتصفح بصلاحيات root إلى تعطيل الـsandbox، لأن العملية لا تستطيع خفض الصلاحيات التي تملكها أصلاً. لذلك تحتوي صورة Playwright على مستخدم عادي اسمه pwuser. في Ubuntu 24.04 والإصدارات الأحدث، يقيّد AppArmor مساحات أسماء المستخدمين غير المميّزين، ويُرفض تشغيل ملف Chromium الثنائي الموجود في مسار لا يغطيه أي ملف تعريف مضمّن. ويُعد تنزيل Playwright ضمن ~/.cache/ms-playwright مساراً من هذا النوع. تحقّق من السببين:
id -u
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo dmesg | grep -i userns_createتؤكد قيمة 1 من sysctl، مع سطر من النواة يحتوي على apparmor="DENIED" operation="userns_create"، السبب الثاني. اسمح لملف ثنائي واحد فقط في /etc/apparmor.d/pw-chromium، مع إبقاء التقييد مفروضاً على كل ما عداه في الخادم:
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. يتضمن المسار إصدار المتصفح، ولذلك يتغير مع كل ترقية لـPlaywright. وتستمر أنماط glob أعلاه في العمل مع هذه التغييرات. أما ملف التعريف الذي يستهدف مساراً واحداً محدداً، فسيتوقف عن المطابقة بصمت، ويبدأ المتصفح بالفشل مجدداً بعد تحديث يبدو غير مرتبط بالمشكلة.
لماذا تعود لقطات الشاشة فارغة أو مليئة بالمربعات
تكون لقطة الشاشة الفارغة، أو المليئة بالمستطيلات الفارغة، ناتجة عادةً عن مشكلة في الخطوط، لا عن خلل في التصيير. يستدعي 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 أو إلى خط بديل لا يحتوي على محارف فعلية، فثبّت fonts-noto-core وfonts-noto-cjk، ثم أعد تشغيل الفحص. يخزّن fontconfig النتائج مؤقتاً، لذلك أعد تشغيل المتصفح بعد تثبيت الخطوط. تسجّل الصورة المجرّدة التي لا تحتوي على أي خطوط Fontconfig error: Cannot load default config file عند بدء التشغيل، وتعرض كل صفحة فارغة.
الإعدادات المحلية والمنطقة الزمنية منفصلتان عن الخطوط، وهما تغيّران محتوى الصفحة، لا مظهرها فقط. تكون LANG غير مضبوطة عادةً داخل الحاوية، وتكون TZ مضبوطة على UTC، لذلك تعرض المواقع محتوى باللغة الإنجليزية وتطبع طوابع زمنية بتوقيت UTC، كما يبلّغ وكيلك عن أوقات لا تتطابق مع ما يراه شخص في ذلك البلد. اضبطهما لكل سياق متصفح بدلاً من ضبطهما لكل جهاز، بحيث يستطيع متصفح واحد تنفيذ المهام لمناطق مختلفة.
const context = await browser.newContext({
locale: 'en-GB',
timezoneId: 'Europe/Paris',
});لماذا تتسبب عمليات المتصفح المتسربة في استخدام الخادم للتبديل
توجد مشكلتان مختلفتان تحملان اسم «zombie». العملية zombie الحقيقية هي عملية انتهت، لكن العملية الأصلية لم تستدعِ wait(). تحتفظ هذه العملية بإدخال PID فقط ولا تحتفظ بأي شيء آخر، لذلك لا تستهلك الذاكرة. تظهر هذه العمليات عندما يعمل المتصفح كـPID 1 داخل حاوية، لأن PID 1 لا يملك آلية افتراضية لجمع العمليات المنتهية. يعالج الخيار --init في Docker هذه المشكلة تحديداً، إذ يشغّل عملية init صغيرة «تمرر الإشارات وتجمع العمليات». وفي Compose يكون الإعداد نفسه هو init: true.
أما التسرب الذي يجعل الخادم يستخدم swap فعلياً، فهو مختلف: عمليات Chromium ما زالت قيد التشغيل ولم يغلقها أي طرف. يحدث ذلك عندما يرمي أحد المهام استثناءً بين newContext() وclose()، أو عندما يُقتل البرنامج المتحكم ويترك شجرة المتصفح التابعة له يتيمة. وأسوأ الحالات هي الشيفرة التي تشغّل متصفحاً جديداً لكل طلب. احسب عددها:
pgrep -c -f 'headless_shell|chrome'
ps -eo pid,ppid,rss,etime,comm --sort=-rss | head -20يجب أن يعود هذا العدد إلى قيمته عند الخمول بين المهام. إذا ارتفع خلال يوم، فالإصلاح في شيفرتك وليس في خيارات التشغيل: أغلق السياق داخل كتلة finally، وأغلق المتصفح عند SIGTERM، وأعد إنشاء المتصفح بعد عدد ثابت من المهام بدلاً من تشغيل مثيل واحد لمدة شهر. في systemd، يؤدي الإيقاف أو إعادة التشغيل إلى قتل كل شيء في cgroup الخاصة بالوحدة، لذلك يُعد sudo systemctl restart browser.service وسيلة موثوقة لإعادة الضبط. أما المتصفح الذي تشغّله يدوياً داخل مضاعِف طرفية، فلا يملك هذا الضمان، وقد تستمر عملياته اليتيمة بعد انتهاء الجلسة.
ما مقدار RAM الذي يحتاج إليه سياق متصفح واحد
اطرح السؤال بدقة، لأن «متصفحاً واحداً» ليس عملية واحدة. يشغّل Chromium عملية للمتصفح، وعملية GPU، وعمليات مساعدة، وعملية renderer واحدة لكل موقع. كما أن عزل المواقع يمنح إطارات iframe من مواقع مختلفة عمليات renderer مستقلة أيضاً. إنّ BrowserContext عبارة عن مخزن ملفات تعريف ارتباط ومنطقة تخزين منفصلين داخل شجرة العمليات نفسها، لذلك يكون السياق الثاني قليل التكلفة. أما الصفحة الثانية فليست كذلك، لأنها تبدأ عمليات renderer، وقد تبدأ الصفحة المليئة بالإعلانات عدة عمليات.
لذلك، القيمة التي يجب قياسها هي ذروة الذاكرة للشجرة كاملةً ضمن حمل العمل الخاص بك. لا قيمة لرقم من مدونة شخص آخر هنا، لأن الصفحات التي يفتحها وكيلك هي التي تحدد الإجابة. أجرِ القياس على الجهاز الذي ستستخدمه، وباستخدام المواقع التي ستزورها:
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، ثم اقسم الناتج على ذروة الاستخدام المقاسة لكل عامل. لتحديد حجم الجهاز الذي سيشغّل ذلك، راجع مقدار RAM وCPU اللذين يحتاج إليهما VPS الخاص بالوكيل.
طبّق هذا الرقم في موضعين. في الشيفرة، استخدم مجموعة عمال ثابتة أو semaphore، بحيث تُضاف دفعة طلبات الوكيل إلى قائمة انتظار بدلاً من تشغيل متصفحات جديدة. وفي نظام التشغيل، استخدم حداً لـcgroup، حتى لا يؤدي خطأ في قائمة الانتظار إلى تعطيل الجهاز بأكمله:
[Service]
MemoryMax=2G
MemorySwapMax=0
TasksMax=512
Restart=alwaysتكتسب MemorySwapMax=0 أهمية أكبر مما قد يبدو. من دونها، ينقل cgroup الصفحات إلى swap عند بلوغ الحد، فيبقى الجهاز قيد التشغيل بينما تصبح كل الطلبات بطيئة، وهذا أصعب في التشخيص من فشل واضح. عند استخدامها، تقتل النواة شجرة المتصفح داخل cgroup، ثم يعيد systemd تشغيل الوحدة، ويبقى sshd قيد التشغيل. والضوابط نفسها في Compose هي mem_limit وshm_size وinit، كما هو موضح في ضبط حدود الذاكرة في Docker Compose.
أبقِ نقطة نهاية المتصفح خارج الإنترنت العام
يمكن لـPlaywright تشغيل المتصفح كخادم وتسليم وكيلك عنوان WebSocket:
const { chromium } = require('playwright');
const server = await chromium.launchServer({ port: 3000 });
console.log(server.wsEndpoint());لا تتضمن نقطة النهاية هذه تسجيل دخول. توضح وثائق API الخاصة بـPlaywright ذلك مباشرة: «يمكن لأي عملية أو صفحة ويب (بما في ذلك العمليات والصفحات التي تعمل داخل Playwright) تعرف قيمة wsPath التحكم في مستخدم نظام التشغيل». المضيف الافتراضي هو localhost، الذي «يقبل الاتصالات من واجهة loopback فقط»، وتحذّر الوثائق من أن تمرير عنوان صريح مثل 0.0.0.0 «يعرّض RPC الخاص بالمتصفح لأي جهة يمكنها الوصول إلى المنفذ الذي يستمع عليه». أما --remote-debugging-port الخاص بـChrome فهو أسوأ. لا يتضمن بروتوكول DevTools أي نوع من المصادقة، ويعتمد بالكامل على ربطه بواجهة loopback.
تحقق مما نشرته فعلياً، وتحقق منه أيضاً من جهاز ثانٍ، وليس من VPS فقط:
ss -ltnpأي شيء يعمل على منفذ متصفح مرتبط بـ0.0.0.0 يُعد مشكلة أمنية. تذكّر أن معظم مزوّدي الخدمة يشغّلون جداراً نارياً منفصلاً للشبكة في لوحة التحكم لديهم، ولا تعرف قواعد ufw شيئاً عنه. يمكنك الوصول إلى نقطة النهاية من جهاز آخر عبر نفق SSH أو VPN خاص بدلاً من ذلك:
ssh -N -L 3000:127.0.0.1:3000 you@your-vpsالخطر هنا أكبر من احتمال أن يسرق أحدهم وقت المتصفح. فالمتصفح الذي يمكنك قيادته هو أداة لتنفيذ تزوير الطلبات، وتعمل من داخل شبكتك. يمكن لأي شخص يصل إلى ذلك المقبس أن يطلب منه جلب http://127.0.0.1:8080 أو صفحة إدارة قاعدة البيانات أو عنوان بيانات التعريف السحابية في 169.254.169.254، ثم يقرأ الاستجابة من الصفحة. سيرى جدارك الناري أن الطلب صادر من VPS نفسه، ولذلك سيسمح به. تعامل مع نقطة التحكم كما لو كانت وصولاً إلى shell على ذلك الخادم.
تتبع خوادم MCP النمط نفسه. يقدّم npx @playwright/mcp@latest --headless --port 8931 الخدمة عبر HTTP على localhost، و--host 0.0.0.0 هو الخيار الذي يحوّل أداة محلية إلى أداة عامة. يذكر ملف README الخاص بالمشروع بوضوح أن Playwright MCP «ليس حدّاً أمنياً». أبقِ المنفذ على loopback، ودَع الوكيل يصل إليه عبر النفق نفسه.
الصفحات التي يقرأها وكيلك مدخلات غير موثوقة
يُدخل الوكيل الذي يتصفح الويب المفتوح نصاً كتبه أشخاص مجهولون إلى نموذج يحتفظ أيضاً بتعليماتك. قد تحتوي الصفحة على نص موجّه إلى ذلك النموذج، يطلب منه التخلي عن المهمة، أو استدعاء أداة، أو نشر بيانات إلى عنوان URL. يتلقى النموذج النصين معاً، لذلك لا توجد لديه طريقة موثوقة للتمييز بين كلمات الصفحة وكلماتك. صمّم الإعداد بحيث لا تتيح الصفحة الخبيثة سوى مجال محدود للتأثير.
- شغّل المتصفح تحت مستخدم نظام تشغيل مستقل، من دون مفاتيح SSH ومن دون بيانات اعتماد سحابية في بيئته.
- استخدم سياقاً جديداً لكل مهمة، و
--isolatedمع Playwright MCP، حتى لا تكون جلسة على موقع ما متاحة للصفحة التالية. - احتفظ بقائمة سماح للمصادر عندما تسمح المهمة بذلك. يقبل Playwright MCP الخيارين
--allowed-originsو--blocked-originsكقائمتين مفصولتين بفواصل منقوطة. - اشترط إجراءً بشرياً قبل أي عملية تغيّر الحالة، مثل إرسال البريد أو إنفاق المال.
والأفضل من ذلك أن تشغّل المتصفح بالكامل على جهاز يمكنك التخلص منه وإعادة بنائه. وهذا هو المبدأ نفسه الذي ينطبق على تشغيل وكلاء البرمجة في جهاز افتراضي مؤقت. إذا كانت المهمة الفعلية للوكيل هي البحث لا التصفح المفتوح، فالأداة الأضيق نطاقاً أكثر أماناً من المتصفح الكامل: يعرض مَهمّة بحث مدعومة بخدمة SearXNG الخاصة بك النتائج من دون تحميل الصفحة الخبيثة على الإطلاق.
FAQ
لماذا يتعطل Chromium في Docker بينما يعمل بشكل طبيعي على VPS نفسه مباشرة؟
لأن الحاوية تحصل تلقائياً على /dev/shm بحجم 64 MB، بينما يحتوي المضيف على مساحة أكبر بكثير. يمرر Chromium المحتوى المعروض عبر منطقة الذاكرة المشتركة هذه، لذلك تملؤها الصفحة الثقيلة ويتوقف renderer. نفّذ df -h /dev/shm داخل الحاوية للتأكد، ثم شغّله باستخدام --ipc=host، الذي يستخدم الذاكرة المشتركة للمضيف، أو باستخدام --shm-size=1g، الذي يزيد مساحة الحاوية الخاصة. ينقل --disable-dev-shm-usage المشكلة فقط إلى /tmp.
هل استخدام --no-sandbox آمن إذا كان VPS لا يشغّل أي شيء آخر؟
لا. تمنع sandbox الصفحة الخبيثة من الوصول إلى بقية الجهاز، وتوضح وثائق Chromium أن هذا الخيار "يعطّل ميزات الأمان الحرجة في Chromium ويجب عدم استخدامه مطلقاً عند تصفح الويب المفتوح". والوكيل الذي يتبع الروابط يتصفح الويب المفتوح. أصلح السبب بدلاً من ذلك: لا تشغّل المتصفح بصلاحيات root، وفي Ubuntu 24.04 أضف ملف تعريف AppArmor يتضمن userns, لمسار البرنامج الثنائي للمتصفح، للسماح بمساحات أسماء المستخدمين غير المميّزة لهذا البرنامج فقط.
كم عدد المتصفحات التي يمكنني تشغيلها على VPS صغير؟
قِس ذلك، ولا تنسخ رقماً جاهزاً. يبدأ Chromium عملية renderer واحدة لكل موقع، لذلك يعتمد العدد على الصفحات التي تفتحها. شغّل عاملاً واحداً باستخدام systemd-run مع ضبط MemoryMax، واقرأ القيمة القصوى من السطر Memory: في systemctl status، ثم اقسم ذاكرة RAM الحرة على هذه القيمة واترك هامشاً احتياطياً. طبّق الحد مرتين: باستخدام queue في التعليمات البرمجية، وMemoryMax في ملف الوحدة، لكي تنتظر الطلبات عند حدوث دفعة مفاجئة بدلاً من دفع الجهاز إلى استخدام swap.
هل يمكن للوكيل الاتصال بالمتصفح من جهاز آخر؟
نعم، لكن لا تربط المنفذ مطلقاً بـ0.0.0.0. يقبل كل من نقطة نهاية خادم Playwright ومنفذ Chrome DevTools أي عميل يمكنه الوصول إليهما، من دون كلمة مرور. أبقِ المستمع على 127.0.0.1، وانقل الاتصال عبر نفق SSH أو VPN خاص. تحقّق باستخدام ss -ltnp على الخادم، واختبر المنفذ من خارج الخادم، وراجع أيضاً جدار الشبكة المنفصل لدى مزود الخدمة.
لماذا تكون لقطات الشاشة فارغة رغم أن الصفحة تحمّلت بوضوح؟
الخطوط مفقودة. عند عدم وجود خط يغطي نظام كتابة الصفحة، يظهر النص كمربعات فارغة أو لا يظهر مطلقاً، لذلك تبدو الصفحة التي تحتوي على صور قليلة فارغة. نفّذ fc-match "sans-serif:lang=ko" لكل لغة تجمعها، وثبّت fonts-noto-core وfonts-noto-cjk عندما تكون النتيجة خطاً بديلاً عاماً، ثم أعد تشغيل المتصفح لكي يعيد fontconfig تحميل ذاكرته المؤقتة. تسجّل الحاوية التي لا تحتوي على أي خطوط Fontconfig error: Cannot load default config file عند بدء التشغيل.