SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-21

Moli zelf hosten: lichtgewicht headless browser voor AI

Bespaar geheugen op uw VPS door Moli te gebruiken in plaats van headless Chrome. Leer hoe u CDP op loopback configureert en ontdek welke webpagina's niet werken met deze engine.

Een headless browser die op een kleine VPS past

Moli is een headless browser voor AI-agents en is compact genoeg om zelf te hosten op een VPS waar headless Chrome niet op past. Het is een browser-engine geschreven in Rust, geen wrapper rond Chromium, en het ondersteunt het Chrome DevTools Protocol (CDP), het protocol dat uw automatiseringsbibliotheek al gebruikt. U installeert één binary, voert moli serve uit en wijst vervolgens Playwright of uw eigen agent-code naar http://127.0.0.1:9222.

Lees de afwegingen voordat u iets installeert. Het project verwoordt zijn eigen reikwijdte duidelijk: geen GUI-browser, geen GPU-compositor, geen pixel-perfecte pariteit met Chrome en geen ondersteuning voor complexe Canvas-elementen of media-afspelen. Pagina's die deze functies vereisen, zullen falen. Echte Chrome onder Playwright blijft het alternatief, en de laatste sectie laat zien hoe u bepaalt welke pagina's dat vereisen.

Elk commando hieronder is afkomstig uit de README van het project en de gepubliceerde skill-bestanden, gecontroleerd in augustus 2026. Elk getal in de grafieken is een cijfer dat het project zelf over zijn engine heeft gepubliceerd, geen meting van deze site, en elk bijschrift bij de grafiek vermeldt dit. Als u nog een engine moet kiezen, behandelt het bredere overzicht van headless browsers voor agents op een VPS de alternatieven.

Waarom verbruikt headless Chrome zoveel geheugen?

Chrome is een browser met een multi-process architectuur. Elk tabblad en elk cross-site iframe krijgt een eigen renderer-proces, en elke renderer heeft een eigen V8-heap en grafische buffers. Dat ontwerp is geschikt voor een desktopomgeving, waar een vastgelopen tabblad niet het hele venster mag laten crashen. Op een VPS met 2 GB RAM betekent dit dat een enkele browse-actie meer geheugen kan kosten dan de applicatie die u daadwerkelijk uitvoert.

Het project heeft 192 diverse publieke URL's gecrawld met vier engines en de resultaten gepubliceerd.

ChartMixed public web crawl, 192 URLs, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Moli",
    "useful_pages": 103,
    "median_rss_mib": 73
  },
  {
    "engine": "Chrome Headless",
    "useful_pages": 101,
    "median_rss_mib": 773
  },
  {
    "engine": "Lightpanda",
    "useful_pages": 85,
    "median_rss_mib": 40
  },
  {
    "engine": "Obscura",
    "useful_pages": 57,
    "median_rss_mib": 39
  }
]

Chrome Headless leverde 101 bruikbare pagina's op en Moli leverde 103 op; in die steekproef lazen beide engines dus ongeveer hetzelfde deel van het web. Het geheugengebruik is waar ze verschillen: een mediane RSS (resident set size, het geheugen dat een proces daadwerkelijk in het RAM vasthoudt) van 773 MiB voor Chrome tegenover 73 MiB voor Moli. Deze verhouding is geloofwaardig omdat deze voortvloeit uit de procesarchitectuur. De exacte verhouding op uw pagina's is niet iets waar u zomaar vanuit moet gaan.

De mediaan is niet het getal dat voor problemen zorgt. De piek doet dat wel. Wanneer een server met 2 GB RAM zonder geheugen komt te zitten, kiest de kernel een proces en beëindigt dit, waarna de melding in dmesg -T of journalctl -k verschijnt:

Out of memory: Killed process 4211 (chrome) total-vm:2318936kB, anon-rss:1418324kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3540kB oom_score_adj:0

Uw agent ziet die regel nooit. Deze ziet een browser die niet meer reageert, meestal als een Playwright-fout zoals page.goto: Page crashed of een gesloten target. Niets in die foutmelding verwijst naar geheugen, en daarom is de OOM (out of memory) killer het eerste wat u moet controleren wanneer een agent willekeurig faalt op een kleine server. Dimensioneren voor de piek is dezelfde oefening als RAM en CPU kiezen voor een agent-VPS.

De Moli-binary installeren, vastgezet op één versie

Het project publiceert een shell-installer en vooraf gecompileerde tarballs op GitHub releases. Sinds augustus 2026 is de huidige release 1.0.1, gepubliceerd op 18 augustus 2026. De benchmarkcijfers in deze handleiding zijn door het project gemeten op 0.1.1; beschouw deze dus als een indicatie van de engine en niet als een garantie voor de build die u installeert.

Zet de versie vast. Een installer die altijd latest ophaalt, verplaatst uw agent bij de volgende rebuild naar een andere browser-engine. Een verandering in browsergedrag is een wijziging die u wilt inplannen in plaats van onverwacht wilt ontdekken.

De shell-installer is de snelste methode, maar het is de moeite waard om deze te lezen voordat u deze uitvoert.

curl --proto '=https' --tlsv1.2 -fsSL \
  -o /tmp/moli-installer.sh \
  https://github.com/lexmount/moli/releases/download/v1.0.1/moli-installer.sh
less /tmp/moli-installer.sh
sh /tmp/moli-installer.sh

Lees het script voordat u het uitvoert. Het is kort. Het kiest een archief op basis van uw uname -m en pakt vervolgens een enkele binary uit in ~/.local/bin. Op x86_64 gebruikt het moli-x86_64-unknown-linux-gnu.tar.gz, en op een Arm-server gebruikt het het aarch64-archief, waardoor zowel Arm- als x86 VPS-abonnementen worden ondersteund. Stel MOLI_INSTALL_DIR in om op een andere locatie te installeren. Let op wat het script voor de versie bepaalt: de nieuwste release, niet de tag waarvan u het script heeft opgehaald. Dat is prima voor een eerste indruk, maar ongeschikt voor een rebuild die reproduceerbaar moet zijn.

Doe voor permanente installaties handmatig wat de installer doet en specificeer zelf het exacte archief. Dit is tevens de manier om de binary op een locatie te plaatsen waar een systeemservice deze kan bereiken, en het voorkomt dat u een gedownload script direct naar een shell pipe.

cd /tmp
curl --proto '=https' --tlsv1.2 -fsSLO \
  https://github.com/lexmount/moli/releases/download/v1.0.1/moli-x86_64-unknown-linux-gnu.tar.gz
mkdir -p moli-pkg
tar -xzf moli-x86_64-unknown-linux-gnu.tar.gz -C moli-pkg --strip-components=1
sudo install -m 0755 moli-pkg/moli /usr/local/bin/moli
moli --version

moli --version en het printen van de versie die u heeft vastgezet, is de volledige controle. moli: command not found direct na de installer betekent dat de installatiemap niet in uw PATH staat; de installer print een regel met de map die u moet toevoegen.

Eenmalige extractie met moli fetch

Veel taken die een agent aan een browser geeft, komen neer op "laad deze URL en vertel wat er staat". Hiervoor is geen server nodig. moli fetch start de engine, laadt één pagina, schrijft één artifact naar de standaarduitvoer en sluit af, waardoor er tussen aanroepen door geen geheugen bezet blijft.

moli fetch --dump markdown --wait-until networkidle https://example.com
moli fetch --dump semantic_tree_text --wait-selector "main" https://example.com
moli fetch --dump json --wait-until networkidle https://example.com > page.json

De eerste optie print de pagina als Markdown, beginnend met # Example Domain. Markdown is het meest efficiënte formaat voor een model, omdat het de opmaak verwijdert en de tekst behoudt. semantic_tree_text behoudt rollen en structuur; dit is gewenst op pagina's met veel navigatie waar links net zo belangrijk zijn als de tekst. --dump json bevat de HTTP-status en de request-trace; gebruik dit wanneer een fetch leeg terugkomt en u de oorzaak moet achterhalen.

De wachtstrategie bepaalt of u inhoud krijgt of een lege schil. --wait-until networkidle keert terug zodra het netwerk stil is. --wait-until domstable keert terug zodra het DOM stopt met veranderen; dit is de betere keuze op pagina's die op de achtergrond pollen en dus nooit volledig stilvallen. --wait-selector wacht op één selector die u opgeeft. Dit is de enige strategie die iets weet over de pagina die u ophaalt, wat het de meest betrouwbare optie maakt wanneer u het doel kent.

Screenshots en PDF vereisen een echte lay-out, en lay-out staat standaard uit:

moli fetch --layout --dump screenshot https://example.com > page.png
moli fetch --layout --dump screenshot_full https://example.com > full-page.png
moli fetch --layout --dump pdf https://example.com > page.pdf

De README noemt het standaard lay-outbeleid LayoutPolicy::Mock: de geometrie wordt gesimuleerd en er wordt niets gerenderd, omdat lay-out en rendering het meest belastende deel van een browser zijn. Dat is de reden waarom de geheugencijfers hierboven zo laag uitvallen. Het betekent ook dat een lege PNG meestal wordt veroorzaakt door een ontbrekende --layout-vlag in plaats van een defecte pagina.

Voeg --block-private-networks toe voor URL's die uw agent zelf heeft gevonden in plaats van URL's die u zelf heeft gekozen. Een agent die links volgt die hij op een pagina heeft gelezen, kan worden gestuurd naar het ophalen van http://169.254.169.254/ voor cloud-instantie-inloggegevens, of een databasepoort op localhost die nooit bedoeld was voor het web. Die vlag weigert navigatie naar privé-adresruimtes, en --block-cidrs beperkt dit verder. Wanneer de taak bestaat uit crawlen in plaats van het lezen van één pagina, wordt de vorm van die pijplijn behandeld in self-hosted Firecrawl-alternatieven, en de stap daarvoor, het vinden van URL's, wordt behandeld in een SearXNG-gebaseerde zoekvaardigheid voor agents.

Een agent koppelen aan Moli via CDP

Voor een agent die door vele stappen navigeert en klikt, voert u in plaats daarvan de server uit.

moli serve --host 127.0.0.1 --port 9222

127.0.0.1 en poort 9222 zijn de standaardinstellingen, dus een kale moli serve bindt al alleen aan loopback. Schrijf beide toch uit in permanente configuraties, zodat de volgende persoon die uw servicebestand leest niet hoeft te onthouden wat de standaardwaarde was.

Controleer de server voordat u er een client mee verbindt:

curl -s http://127.0.0.1:9222/json/version

Een gezonde server antwoordt met een JSON-object dat een webSocketDebuggerUrl-veld bevat; die URL is waar een CDP-client aan koppelt. curl: (7) Failed to connect to 127.0.0.1 port 9222: Connection refused betekent dat er niets luistert; lees in dat geval de terminal waarin u deze hebt gestart, of gebruik journalctl -u moli -n 50 zodra het een service is. /json/list somt de open targets op en /json/protocol somt de domeinen op die deze build implementeert. Zo achterhaalt u of een CDP-methode waarvan u afhankelijk bent hier bestaat.

Playwright koppelt aan dat eindpunt in plaats van zelf een browser te starten:

import { chromium } from "playwright";

const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();

await page.goto("https://example.com");
console.log(await page.locator("body").innerText());

await browser.close();

De regel die ertoe doet is connectOverCDP, niet chromium.launch(). Er is hier geen onderliggend Chromium-proces, dus executablePath en de gebruikelijke container-flags zoals --no-sandbox hebben geen effect. Proxy-, cookie- en user-agent-instellingen geeft u om dezelfde reden mee als flags aan de Moli-server zelf. Verwacht een geselecteerde CDP-dekking in plaats van het volledige Chrome-protocol: een expliciete unsupported-method-fout is een grens van de engine, geen bug in uw code.

Twee server-flags bepalen wat de agent kan doen. --layout schakelt echte geometrie in, wat nodig is voor kliks op coördinaten en screenshots. --resource haalt de optionele afbeeldingen, lettertypes en media op; dit kost bandbreedte en geheugen bij elke pagina-load, dus laat dit uitgeschakeld totdat een pagina aantoont dat deze ze nodig heeft. --profile-dir behoudt cookies en opslag tussen runs; zonder deze flag is elke run tijdelijk.

ChartOne agent episode, Moli against Chromium, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Moli",
    "cdp_ready_ms": 34.85,
    "peak_pss_mib": 102.46,
    "processes": 1
  },
  {
    "engine": "Chromium",
    "cdp_ready_ms": 169.37,
    "peak_pss_mib": 348.82,
    "processes": 11
  }
]

Bij de voorbeeld-agent-workload van het project accepteerde Moli een CDP-verbinding na 34.85 ms tegenover 169.37 ms voor Chromium, bij een piek-PSS (proportional set size, geheugen geteld met gedeelde pagina's verdeeld over de processen die ze delen) van 102.46 MiB tegenover 348.82 MiB. Het structurele verschil staat in de laatste kolom: 1 proces tegenover 11. Eén proces is één entiteit voor systemd om te superviseren en één cgroup om te begrenzen, wat de volgende sectie kort maakt.

Moli serve uitvoeren als systemd-service op loopback

Voer de server uit als een service wanneer een agent een browser nodig heeft die voor hem klaarstaat. Blijf moli fetch per URL gebruiken wanneer dit niet het geval is, omdat een inactieve server nog steeds geheugen in beslag neemt.

Stel poort 9222 niet bloot aan een publieke interface. CDP bevat geen enkele vorm van authenticatie. Iedereen die die poort kan bereiken, kan de browser aansturen en alles lezen wat de browser kan bereiken, inclusief alle cookies in uw profielmap. Houd deze op 127.0.0.1. Benader de poort vanaf een andere machine via een SSH-tunnel (ssh -L 9222:127.0.0.1:9222 user@your-vps) of via een private VPN-interface, en laat de agent verbinding maken met http://127.0.0.1:9222 aan zijn eigen kant van die tunnel.

Maak een servicegebruiker aan en vervolgens het unit-bestand:

sudo useradd --system --home-dir /var/lib/moli --shell /usr/sbin/nologin moli

Schrijf /etc/systemd/system/moli.service:

[Unit]
Description=Moli headless browser CDP server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=moli
Group=moli
ExecStart=/usr/local/bin/moli serve --host 127.0.0.1 --port 9222 --profile-dir /var/lib/moli/profile --block-private-networks
Restart=on-failure
RestartSec=2
StateDirectory=moli
MemoryAccounting=yes
MemoryMax=768M
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now moli.service
systemctl status moli.service
curl -s http://127.0.0.1:9222/json/version

systemctl status hoort active (running) te tonen en de curl hoort de discovery JSON terug te geven. ProtectSystem=strict koppelt het volledige bestandssysteem alleen-lezen aan voor deze unit; daarom is StateDirectory=moli hier niet optioneel: het maakt /var/lib/moli aan, eigendom van de servicegebruiker, en maakt dat specifieke pad schrijfbaar. Een unit die start en vervolgens stopt met een permissiefout in journalctl -u moli probeert bijna altijd te schrijven naar een locatie die ProtectSystem zojuist alleen-lezen heeft gemaakt. Verplaats dat pad daarom naar de state-directory.

MemoryMax=768M maakt het veilig om dit naast uw applicatie te draaien. De unit krijgt zijn eigen cgroup, en wanneer die cgroup zijn limiet overschrijdt, beëindigt de kernel een proces daarbinnen en laat de rest van het systeem ongemoeid. De journal legt dit vast:

moli.service: A process of this unit has been killed by the OOM killer.

Beschouw die regel als een signaal voor de dimensionering. Of de pagina's zijn zwaarder dan u had gepland, of de limiet is te laag. Stel het getal in op basis van een meting van uw eigen pagina's, wat in de volgende sectie wordt behandeld. Dezelfde accounting-flags begrenzen elke andere service op de server, en geheugen en CPU beperken met systemd werkt op dezelfde wijze voor de overige services.

Meet zelf het piekgeheugengebruik

De gepubliceerde cijfers zijn afkomstig van de hardware en de pagina's van anderen. Het piekgeheugen bepaalt of uw server blijft draaien, en de piek is volledig afhankelijk van wat u laadt. Meet voordat u de omvang bepaalt.

Gebruik voor het eenmalig ophalen van pagina's het time-binary, dat veel meer rapporteert dan de shell-builtin met dezelfde naam:

sudo apt update && sudo apt install -y time
/usr/bin/time -v moli fetch --dump markdown --wait-until networkidle https://example.com > /dev/null

De uitvoer eindigt met een blok resourcestatistieken dat Maximum resident set size (kbytes) bevat. Deel dit door 1024 voor MiB. Voer dit uit over tien pagina's die uw agent daadwerkelijk bezoekt in plaats van over example.com, en behoud het slechtste resultaat in plaats van het gemiddelde, omdat de OOM killer reageert op pieken.

Lees voor de service de teller uit die de kernel al bijhoudt voor de cgroup:

cat /sys/fs/cgroup/system.slice/moli.service/memory.peak
systemd-cgtop -m

memory.peak is een aantal bytes en is de hoogste waarde sinds de laatste start van de unit; een herstart reset deze waarde. Dat cijfer is de ondergrens waar MemoryMax boven moet blijven, met extra ruimte voor de zwaarste pagina die u nog niet heeft bezocht. systemd-cgtop -m toont het actuele gebruik per unit; dit is de snelste manier om te zien welke service op de server vandaag de meeste resources verbruikt.

Waar faalt Moli en wanneer heeft u nog Chrome nodig?

Het project voert ook een benchmark uit van 1.308 vergelijkbare browser-automatiseringstaken en publiceert de score voor verschillende engines.

ChartLexbench headless browser suite, 1,308 tasks, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Chrome",
    "success_rate_pct": 99.85
  },
  {
    "engine": "Moli 0.1.1",
    "success_rate_pct": 81.88
  },
  {
    "engine": "Kitesurf",
    "success_rate_pct": 62.08
  },
  {
    "engine": "Lightpanda",
    "success_rate_pct": 53.29
  },
  {
    "engine": "Obscura",
    "success_rate_pct": 44.88
  }
]

Over die 5 engines heen voltooide Moli 0.1.1 81.88 procent van de taken en Chrome, de referentie-engine, voltooide 99.85 procent. Dit is het project dat zichzelf beoordeelt op zijn eigen suite, dus beschouw het als een bewering in plaats van als een onafhankelijk resultaat.

De praktische interpretatie is eenvoudig. Ongeveer één op de vijf taken die Chrome voltooide, faalde op Moli. Als uw agent een vaste set pagina's bezoekt die u beheert, zegt die verhouding u weinig, omdat uw pagina's werken of niet en daar kunt u vanmiddag achter komen. Als uw agent het open web doorzoekt, is het een reëel faalpercentage waar u uw ontwerp op moet aanpassen.

Wat faalt is voorspelbaar op basis van de gestelde scope van het project.

  • Applicaties die hun interface in een Canvas-element tekenen in plaats van in het DOM, omdat Canvas-getrouwheid expliciet buiten de scope valt
  • Alles wat WebGL of GPU-compositing vereist, omdat er geen GPU-compositor aanwezig is
  • DRM-beveiligde video en veeleisende media-afspeelscenario's
  • Visuele tests die pixel-exacte screenshots vergelijken met Chrome, aangezien pariteit met Chrome geen doelstelling is

Het andere cijfer dat het project noemt, een volledige run die 1,612 miljoen webplatformtests doorstaat, is een uitspraak over de dekking van standaarden. Het is geen garantie voor de sites die uw agent zal bezoeken. Een pagina kan uitsluitend gebruikmaken van goed ondersteunde standaarden en alsnog falen op een bot-check, en geen enkele engine-score dekt dat af.

Houd daarom de fallback in uw ontwerp. Stuur elke URL eerst naar Moli. Wanneer een pagina leeg terugkomt, of een selector nooit verschijnt, probeer die specifieke URL opnieuw met Playwright die een echte Chrome aanstuurt, op een krachtigere machine of volgens een schema waarbij een proces van 773 MiB betaalbaar is. De meeste agents besteden het grootste deel van hun tijd aan gewone pagina's, dus de kleine engine verwerkt het volume en de dure engine vangt de uitzonderingen op.

FAQ

Kan Moli headless Chrome vervangen voor mijn agent?

Voor het lezen van pagina's, het extraheren van tekst en standaard klikacties is het antwoord meestal ja. In de eigen benchmark van het project met 1.308 taken behaalde het 81.88 procent, vergeleken met 99.85 procent voor Chrome. Ongeveer één op de vijf taken vereist dus functionaliteit die Moli niet biedt. Applicaties die via Canvas worden gerenderd, WebGL en DRM-video zijn bekende hiaten. Stuur die URL's door naar een echte Chrome-instantie in plaats van alles terug te draaien.

Hoeveel RAM heeft Moli nodig op een VPS?

Het project rapporteert een mediane RSS van 73 MiB over een crawl van 192 URL's, en een piek-PSS van 102.46 MiB tijdens een voorbeeld-agent-episode, tegenover 773 MiB mediaan voor headless Chrome. Dit zijn hun cijfers op hun pagina's. Meet uw eigen verbruik met /usr/bin/time -v rondom een moli fetch-aanroep voor eenmalig gebruik, of lees /sys/fs/cgroup/system.slice/moli.service/memory.peak voor de service, en stel vervolgens MemoryMax in op een waarde boven de hoogste waarde die u waarneemt.

Is het veilig om poort 9222 bloot te stellen aan het internet?

Nee. CDP heeft geen authenticatie; iedereen die die poort kan bereiken, kan uw browser aansturen en alles lezen wat de browser kan bereiken. Houd --host 127.0.0.1 aan en benader het eindpunt vanaf een andere machine via een SSH-tunnel of over een privé VPN-interface. Als u op een ander adres moet binden, plaats dit dan op een privé-interface en beheer de toegang met de firewall.

Waarom is mijn schermafbeelding leeg of klik ik op niets?

Layout is standaard uitgeschakeld. De README noemt het standaardbeleid LayoutPolicy::Mock, waardoor elementgeometrie niet echt is en alles wat afhankelijk is van een kader op de pagina niet werkt. Start de server met moli serve --layout, of voeg --layout toe aan moli fetch, waarna de schermafbeelding en coördinaatpaden gaan werken. Ontbrekende afbeeldingen zijn een andere vlag: --resource.

Welke versie van Moli moet ik installeren?

Pin één versie vast en noteer welke. Sinds augustus 2026 is de huidige release 1.0.1, terwijl de benchmarkcijfers die het project publiceert zijn gemeten op 0.1.1. De twee zijn dus niet uitwisselbaar wanneer u resultaten vergelijkt met anderen. Download de moli-x86_64-unknown-linux-gnu.tar.gz van die tag en installeer het binaire bestand zelf in plaats van te vertrouwen op het shell-installatieprogramma, dat de nieuwste release ophaalt in plaats van de tag die u heeft binnengehaald. Bevestig daarna met moli --version.