[{"data":1,"prerenderedAt":184},["ShallowReactive",2],{"seo-verification":3,"blog-self-host-gotenberg-on-a-vps-pdf-generation-api-for-developers-ma-en":6,"promo-bar-likely":183},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-self-host-gotenberg-on-a-vps-pdf-generation-api-for-developers-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":28,"featuredImage":30,"bgImage":31,"posterImage":32,"relatedSolution":33,"intro":36,"sections":37,"ctaTitle":129,"ctaBody":130,"ctaButton":131,"ctaUrl":132,"relatedPosts":133},275,"self-host-gotenberg-on-a-vps-pdf-generation-api-for-developers",{"fr":12,"en":10,"ar":13,"es":14},"heberger-gotenberg-vps","استضافة-gotenberg-على-vps-واجهة-توليد-pdf-للمطورين","alojar-gotenberg-en-un-vps","Self-Host Gotenberg on a VPS: PDF Generation API for Developers","Deploy Gotenberg 8.x on your VPS: self-hosted REST API to generate PDFs from HTML, URLs, DOCX and Markdown. Complete guide with nginx, Laravel, Python, Node.js.",8,1,false,"2026-01-16T00:00:00+00:00","2026-11-04T16:30:27+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},4,"Development","developpement","bg-warning\u002F10 text-warning","dev",[29],{"id":23,"name":24,"slug":25,"color":26,"icon":27},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fheberger-gotenberg-vps-poster.svg",{"categorySlug":34,"appSlug":35},"development","gotenberg","Every SaaS PDF service charges per page or per document. Gotenberg does not. It is an open-source MIT-licensed HTTP microservice that generates PDFs from HTML, URLs, Markdown and LibreOffice documents, with Chromium and LibreOffice bundled in a single Docker container. This guide covers the full deployment on a VPS — API routes, nginx reverse proxy, PHP\u002FPython\u002FNode.js integration examples, advanced configuration and troubleshooting common errors.",[38,42,76,86,89,108,111,114,117,120,123,126],{"type":39,"title":40,"body":41},"h2","Why self-host a PDF generation API?","SaaS PDF services (Puppeteer Cloud, PDFShift, DocRaptor) charge per document, per page or by API call volume. As soon as your application generates invoices, reports or contracts at scale, the cost grows linearly with your usage. Gotenberg replaces them with a fixed monthly VPS cost, regardless of how many PDFs you generate. It also keeps your documents private: nothing leaves your infrastructure during conversion.",{"type":43,"title":44,"headers":45,"rows":51},"comparison","Gotenberg, WeasyPrint, Stirling-PDF or Puppeteer?",[46,47,48,49,50],"Tool","Source formats","Min RAM","REST API","Licence",[52,58,64,70],[53,54,55,56,57],"Gotenberg 8.x","HTML, URL, DOCX\u002FODT\u002FXLSX, Markdown, images","2 GB (Chromium)","Yes — multipart\u002Fform-data","MIT",[59,60,61,62,63],"WeasyPrint","HTML + CSS (no JS)","256 MB","No — Python lib\u002FCLI","BSD",[65,66,67,68,69],"Stirling-PDF","PDF (edit, OCR, merge)","512 MB","Yes — JSON\u002Fmultipart","MIT (AGPLv3 for Pro)",[71,72,73,74,75],"Puppeteer alone","HTML + URL via headless Chrome","1.5 GB","No — Node.js lib","Apache 2.0",{"type":77,"title":78,"items":79},"ul","What Gotenberg can convert",[80,81,82,83,84,85],"HTML to PDF via a full Chromium instance — CSS, web fonts, JavaScript and print media queries are all honoured.","Any URL to PDF — archive dashboard views, generate weekly reports or capture payment receipts.","LibreOffice documents (DOCX, XLSX, PPTX, ODT, ODS, ODP and 20+ formats) to PDF without installing Microsoft Office.","Markdown to PDF via the Chromium route with configurable CSS.","Multiple PDFs merged into a single file in one API call.","Images (PNG, JPEG, WebP, TIFF) converted to PDF.",{"type":39,"title":87,"body":88},"Requirements","A VPS with at least **1 vCPU** and **2 GB RAM** (Chromium alone consumes 1.2–1.5 GB during rendering; 1 GB RAM is insufficient as soon as a conversion is in progress). Ubuntu 22.04 or 24.04 with Docker and Docker Compose v2 installed. Gotenberg listens on **port 3000** by default. No domain is needed if you call the API from the same server — `http:\u002F\u002F127.0.0.1:3000` is enough. To expose it to other services, an nginx reverse proxy is recommended (see below).",{"type":90,"title":91,"steps":92},"steps","Deploy Gotenberg step by step",[93,96,99,102,105],{"title":94,"body":95},"Create the compose file","SSH into your VPS and create a directory:\n```\nmkdir gotenberg && cd gotenberg\n```\nCreate `docker-compose.yml`:\n```yaml\nservices:\n  gotenberg:\n    image: gotenberg\u002Fgotenberg:8\n    restart: unless-stopped\n    ports:\n      - \"127.0.0.1:3000:3000\"\n    command:\n      - gotenberg\n      - --chromium-disable-javascript=false\n      - --api-timeout=30s\n      - --chromium-max-queue-size=10\n```\nBinding to `127.0.0.1` prevents direct access from the internet. As of 2026, the stable release is **8.x** (8.9.1 and above) — the `:8` image tracks the latest 8.x.",{"title":97,"body":98},"Start the container","Run `docker compose up -d`. Gotenberg pulls the image (~1.5 GB, includes Chromium and LibreOffice) and starts the HTTP server. Check the health endpoint:\n```\ncurl http:\u002F\u002F127.0.0.1:3000\u002Fhealth\n```\nA `{\"status\":\"up\"}` response confirms the service is ready.",{"title":100,"body":101},"Generate your first PDF from HTML","Create a minimal HTML file (`invoice.html`) and convert it:\n```\ncurl -s --request POST http:\u002F\u002F127.0.0.1:3000\u002Fforms\u002Fchromium\u002Fconvert\u002Fhtml \\\n  --form 'index.html=@invoice.html' \\\n  -o invoice.pdf\n```\nThe response is the PDF as a binary stream — pipe it to a file, a response object, or an S3 upload.",{"title":103,"body":104},"Convert a Word document to PDF","Upload a DOCX file to the LibreOffice route:\n```\ncurl -s --request POST http:\u002F\u002F127.0.0.1:3000\u002Fforms\u002Flibreoffice\u002Fconvert \\\n  --form 'files=@contract.docx' \\\n  -o contract.pdf\n```\nGotenberg uses the embedded LibreOffice engine for conversion — no Microsoft Office licence required.",{"title":106,"body":107},"Add authentication (recommended)","Gotenberg has no built-in auth. The recommended approach: install nginx, create a reverse proxy from `https:\u002F\u002Fgotenberg.your-domain.com` to `http:\u002F\u002F127.0.0.1:3000`, and add HTTP basic auth. Only your application's backend should call this endpoint — never expose it unauthenticated to the public internet, as it can be abused to render arbitrary URLs.",{"type":39,"title":109,"body":110},"API routes and formats","Gotenberg exposes two families of REST routes, all `POST multipart\u002Fform-data`.\n\n**Chromium routes** — headless browser rendering:\n- `POST \u002Fforms\u002Fchromium\u002Fconvert\u002Fhtml` — conversion from a local HTML file (field `index.html`); CSS\u002FJS\u002Fimage assets can be attached as additional fields.\n- `POST \u002Fforms\u002Fchromium\u002Fconvert\u002Furl` — conversion from a public URL (field `url`); Chromium loads the full page, JavaScript included.\n- `POST \u002Fforms\u002Fchromium\u002Fconvert\u002Fmarkdown` — Markdown conversion with optional stylesheet.\n\n**LibreOffice routes** — office document conversion:\n- `POST \u002Fforms\u002Flibreoffice\u002Fconvert` — accepts DOCX, XLSX, PPTX, ODT, ODS, ODP and 20+ formats; multiple files merged into one PDF if you send `merge=true`.\n\n**PDF routes** — existing PDF manipulation:\n- `POST \u002Fforms\u002Fpdfengines\u002Fmerge` — merges multiple PDF files into one.\n- `POST \u002Fforms\u002Fpdfengines\u002Fconvert` — converts to PDF\u002FA (long-term archiving).\n\n**Webhook**: instead of receiving the PDF as a synchronous response, pass a `Gotenberg-Webhook-Url` header — Gotenberg then `POST`s the PDF to your URL as soon as the conversion is done, freeing the client connection immediately. Useful for long conversions or asynchronous architectures.",{"type":39,"title":112,"body":113},"nginx reverse proxy","To expose Gotenberg to other services or call it from a dedicated domain, add a `location` block to your nginx configuration:\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name gotenberg.your-domain.com;\n\n    ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fgotenberg.your-domain.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fgotenberg.your-domain.com\u002Fprivkey.pem;\n\n    location \u002F {\n        proxy_pass         http:\u002F\u002F127.0.0.1:3000;\n        proxy_set_header   Host $host;\n        proxy_set_header   X-Real-IP $remote_addr;\n        proxy_read_timeout 60s;\n        client_max_body_size 50M;\n\n        auth_basic           \"Gotenberg API\";\n        auth_basic_user_file \u002Fetc\u002Fnginx\u002F.htpasswd;\n    }\n}\n```\n\nIf Gotenberg runs on the same server as your backend, prefer a direct call to `http:\u002F\u002F127.0.0.1:3000` — less latency and no need to expose the service on a domain.",{"type":39,"title":115,"body":116},"Advanced integration: PHP, Python, Node.js","Gotenberg is language-agnostic — any HTTP client capable of sending `multipart\u002Fform-data` works.\n\n**PHP \u002F Laravel** — use the official `gotenberg-php` package:\n```php\nuse Gotenberg\\Gotenberg;\nuse Gotenberg\\Stream;\n\n$request = Gotenberg::chromium('http:\u002F\u002F127.0.0.1:3000')\n    ->pdf()\n    ->html(Stream::path('\u002Fpath\u002Fto\u002Finvoice.html'));\n\n$response = $client->sendRequest($request);\nfile_put_contents('invoice.pdf', $response->getBody());\n```\n\n**Python** — with the `gotenberg-client` library:\n```python\nfrom gotenberg_client import GotenbergClient\n\nwith GotenbergClient('http:\u002F\u002F127.0.0.1:3000') as client:\n    with client.chromium.html_to_pdf() as route:\n        route.index(Path('invoice.html'))\n        response = route.run()\n        Path('invoice.pdf').write_bytes(response.content)\n```\n\n**Node.js** — with native `fetch` (Node 18+):\n```js\nconst form = new FormData();\nform.append('index.html', new Blob([htmlContent], { type: 'text\u002Fhtml' }), 'index.html');\n\nconst res = await fetch('http:\u002F\u002F127.0.0.1:3000\u002Fforms\u002Fchromium\u002Fconvert\u002Fhtml', {\n  method: 'POST',\n  body: form,\n});\nconst pdf = Buffer.from(await res.arrayBuffer());\nfs.writeFileSync('invoice.pdf', pdf);\n```\n\nFor Node.js, the `chromiumly` package wraps the full API if you prefer to avoid manual `FormData` handling.",{"type":39,"title":118,"body":119},"Advanced configuration","Parameters are passed as arguments to the `gotenberg` command in your `docker-compose.yml` (or via `GOTENBERG_`-prefixed environment variables).\n\n**Timeout**: `--api-timeout=30s` — maximum duration of a conversion. Increase it (e.g. `60s`) for long DOCX documents or pages with heavy JavaScript.\n\n**Concurrency**: `--chromium-max-queue-size=10` — maximum number of Chromium conversions in queue. Beyond this, Gotenberg responds `503` rather than accumulating in memory. Adjust based on available RAM (each Chromium conversion opens an ephemeral instance).\n\n**Rate limiting**: `--api-port-from-env` combined with an nginx reverse proxy (`limit_req_zone`) is the recommended approach to throttle incoming calls.\n\n**Logs**: Gotenberg logs in JSON to stdout — `docker compose logs gotenberg` gives you the detail of each conversion (duration, route, status). For more verbose output:\n```yaml\ncommand:\n  - gotenberg\n  - --log-level=debug\n```\n\n**Disable LibreOffice** (if you only need Chromium, saves RAM):\n```yaml\ncommand:\n  - gotenberg\n  - --libreoffice-disable-listener=true\n```",{"type":121,"body":122},"tip","For high-volume architectures, run multiple Gotenberg replicas behind an nginx load balancer (`upstream`): each instance holds its own Chromium workers, and you scale horizontally without shared state.",{"type":39,"title":124,"body":125},"Troubleshooting common errors","**Chromium timeout — `context deadline exceeded`**: the HTML conversion exceeds the configured timeout. Common causes: page with slow JavaScript, external resources (CDN, Google Fonts) unreachable from the server, or oversized DOM. Fix: increase `--api-timeout`, host your assets locally and attach them as multipart fields rather than loading them via URL.\n\n**Port 3000 already in use**: another application is listening on 3000. Change the mapping in `docker-compose.yml`: `\"127.0.0.1:3001:3000\"` and update the URL in your calls.\n\n**File too large — `413 Request Entity Too Large`**: nginx cuts the request before Gotenberg. Increase `client_max_body_size` in the nginx block (e.g. `100M` for large XLSX files).\n\n**Out of memory — `OOMKilled`**: Docker kills the container when Chromium exceeds available RAM. Check with `docker inspect gotenberg | grep OOMKilled`. Fixes: add RAM to the VPS, limit concurrency (`--chromium-max-queue-size=3`), or disable JavaScript if your HTML does not require it (`--chromium-disable-javascript=true`).\n\n**LibreOffice error — corrupted file or unrecognised format**: some DOCX files generated by third-party tools do not conform to the OOXML standard. First verify the file opens without errors in LibreOffice Desktop. The Gotenberg log header gives the exact LibreOffice message (`docker compose logs gotenberg --tail 50`).",{"type":39,"title":127,"body":128},"Integrate with your application","Gotenberg is language-agnostic — any HTTP client works. Libraries exist for PHP (`gotenberg-php`), Node.js (`chromiumly`), Python (`gotenberg-client`), Go and others. For Laravel, a first-party package wraps the API. In Node.js, you can call it with `axios` or the native `fetch` API by POSTing a `FormData` object with the HTML content or file. See the advanced integration section above for ready-to-use code examples in each language.","Deploy Gotenberg on a ServOrbit Cloud VPS in minutes","With a ServOrbit Cloud VPS you get Docker preconfigured and scalable RAM — the right base for Gotenberg to handle Chromium rendering and LibreOffice conversion without resource contention.","Launch my Cloud VPS","\u002Fapplications\u002Fdevelopment\u002Fgotenberg",[134,159],{"id":135,"slug":136,"slugs":137,"title":141,"excerpt":142,"readTime":143,"views":144,"isPinned":19,"noindex":19,"publishedAt":145,"updatedAt":146,"category":147,"categories":153,"featuredImage":30,"bgImage":31,"posterImage":155,"relatedSolution":156},178,"self-host-stirling-pdf-on-a-vps-50-pdf-operations-no-cloud",{"fr":138,"en":136,"ar":139,"es":140},"self-host-stirling-pdf-vps","استضافة-stirling-pdf-على-vps-أكثر-من-50-عملية-بلا-سحابة","alojar-stirling-pdf-en-un-vps","Self-Host Stirling PDF on a VPS: 50+ PDF Operations, No Cloud","Self-host Stirling PDF on a VPS — the #1 PDF app on GitHub (87 k stars, MIT). Merge, split, compress, convert, OCR and 50+ operations. No cloud upload, no subscription.",5,0,"2026-07-13T00:00:00+00:00","2026-11-04T16:30:26+00:00",{"id":148,"name":149,"slug":150,"color":151,"icon":152},7,"Self-hosting","self-hosting","bg-indigo-500\u002F10 text-indigo-400","cloud",[154],{"id":148,"name":149,"slug":150,"color":151,"icon":152},"\u002Fblog\u002Fcovers\u002Fself-host-stirling-pdf-vps-poster.svg",{"categorySlug":157,"appSlug":158},"collaboration-productivity","stirling-pdf",{"id":160,"slug":161,"slugs":162,"title":166,"excerpt":167,"readTime":168,"views":18,"isPinned":19,"noindex":19,"publishedAt":169,"updatedAt":170,"category":171,"categories":177,"featuredImage":30,"bgImage":31,"posterImage":179,"relatedSolution":180},61,"hosting-appwrite-on-a-vps",{"fr":163,"en":161,"ar":164,"es":165},"heberger-appwrite-vps","استضافة-appwrite-على-خادم-vps","alojar-appwrite-en-un-vps","Hosting Appwrite on a VPS: complete self-hosted guide","Deploy Appwrite on your VPS with Docker: full stack, Traefik SSL, backups, hardening, and Supabase comparison.",10,"2026-04-20T00:00:00+00:00","2026-09-19T02:00:43+00:00",{"id":172,"name":173,"slug":174,"color":175,"icon":176},6,"Databases","bases-de-donnees","bg-teal-500\u002F10 text-teal-400","database",[178],{"id":172,"name":173,"slug":174,"color":175,"icon":176},"\u002Fblog\u002Fcovers\u002Fheberger-appwrite-vps-poster.svg",{"categorySlug":181,"appSlug":182},"databases","appwrite",true,1794270485991]