How to run head­less Chrome as sys­temd ser­vice

Published on: 05.10.2022

Sys­temd has been the default init sys­tem in RHEL/Cen­tOS, Fedora, Ubuntu, Debian and other Linux dis­tri­b­u­tions for sev­eral years now. This arti­cle shows how to cre­ate a sim­ple sys­temd ser­vice to run a head­less Chrome instance for remote debug­ging.

Article content


We oper­ate an appli­ca­tion on a Linux based remote server. This appli­ca­tion runs sev­eral tasks which require head­less Chrome for gen­er­at­ing for­mat­ted out­put from HTML pages (PDF doc­u­ments, screen­shots of web pages, ...). Each com­ple­tion of such tasks includes start­ing up a head­less Chrome instance, gen­er­at­ing the out­put, and clos­ing Chrome.

Dur­ing high usage times, a lot of sys­tem resources are required for par­al­lel work­ing tasks. We wish to opti­mize the gen­er­a­tion of doc­u­ments via Chrome by using a sin­gle Chrome instance only that runs as sys­tem ser­vice and serves all tasks of our appli­ca­tion . In order to ensure this Chrome instance doesn't use up too many resources in the long term, we also want it to be restarted on a reg­u­lar base.

To avoid mali­cious JavaScripts on the processed HTML pages to harm our server, we also want to restrict Chrome access to the sys­tem as much as pos­si­ble.



We cre­ate a ded­i­cated user chrome, which can't login nor has a home folder. It serves as a non-ele­vated user to run the Chrome instance:

sudo adduser chrome --shell=/bin/false --no-cre­ate-home


We want to exchange files with the Chrome instance. Chrome will save the gen­er­ated out­put files to an exchange folder, where our appli­ca­tion can pick them up. This folder shall be the only writable folder for Chrome:

# Cre­ate a file exchange folder.
sudo mkdir /var/file­ex­change
# Set folder own­er­ship.
sudo chown chrome:chrome /var/file­ex­change
# Set appro­pri­ate file exchange folder per­mis­sions.
sudo chmod 775 /var/file­ex­change


For run­ning our head­less Chrome in the back­ground, we cre­ate a sys­tem-wide sys­temd file /etc/systemd/system/chrome.service:

Descrip­tion=Head­less chrome

Exec­Start=/opt/chrome --head­less --no-first-run --no-default-browser-check --no-sand­box --dis­able-setuid-sand­box --dis­able-gpu --dis­able-dev-shm-usage --remote-debug­ging-port=9222 --win­dow-size="1920,1200" --force-device-scale-fac­tor=0.5



Key aspects of this ser­vice unit:

  • Type=simple: There's only one main process, which is started with ExecStart.
  • Restart=always: Restart the ser­vice in case of any fail­ures or inter­rup­tions.
  • RuntimeMaxSec=1d: Limit the ser­vice to a one day run­time. The ser­vice will auto­mat­i­cally be stopped and because of above Restart direc­tive restarted.
  • DynamicUser=true: Hard­ens file and process access to the sys­tem. It implies pri­vate tem­po­rary fold­ers and strict sys­tem pro­tec­tion.
  • User=chrome: The sys­tem user that will run the Chrome instance. We use our pre­vi­ously cre­ated non-ele­vated user, rather than a ran­dom user which DynamicUser=true would cre­ate with­out this direc­tive.
  • ProtectSystem=strict: This option is redun­dant. It is implied already by DynamicUser=true. We kept as reminder of a very strict read-only file sys­tem.
  • ReadWritePaths=/var/fileexchange: Space sep­a­rated whitelist for writable fold­ers. Adding our pre­vi­ously cre­ated file exchange folder here ensures Chrome can write to this folder.
  • Environment=HOME=/tmp: Set the home direc­tory to the pri­vate temp files path.
  • Environment=DISPLAY=:0: Explic­itly use the first dis­play by defin­ing the DISPLAY envi­ron­ment vari­able.
  • ExecStart: The path to and start options for our head­less Chrome instance. You may want to replace the path with your actual Chrome path and also adjust the Chrome startup flags accord­ing to your require­ments.
  • WantedBy=multi-user.target: Start the ser­vice after all net­work ser­vices are avail­able and when the sys­tem is ready for login.


Set proper file per­mis­sions for the sys­temd ser­vice file:

sudo chmod 644 /etc/sys­temd/sys­tem/chrome.ser­vice


Reload the unit files to make sys­temd know about the new ser­vice:

sudo sys­tem­ctl dae­mon-reload


Start the ser­vice and observe any error mes­sages (e.g., when the path to Chrome isn't cor­rect, the cho­sen remote debug­ging port already used, with a typo in Chrome startup flags, and so on):

sudo sys­tem­ctl start chrome.ser­vice


If no errors occurred and the ser­vice is run­ning fine, enable it for auto­matic start on machine boot:

sudo sys­tem­ctl enable chrome.ser­vice



We suc­cess­fully cre­ated a sys­temd ser­vice for head­less Chrome that

  • restarts daily,
  • starts with a non-ele­vated user,
  • is mostly iso­lated from the remain­ing sys­tem, but
  • can cre­ate files for processed out­put in a specif­i­cally defined loca­tion.