We operate an application on a Linux based remote server. This application runs several tasks which require headless Chrome for generating formatted output from HTML pages (PDF documents, screenshots of web pages, ...). Each completion of such tasks includes starting up a headless Chrome instance, generating the output, and closing Chrome.
During high usage times, a lot of system resources are required for parallel working tasks. We wish to optimize the generation of documents via Chrome by using a single Chrome instance only that runs as system service and serves all tasks of our application . 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 regular base.
We create a dedicated user
chrome, which can't login nor has a home folder. It serves as a non-elevated user to run the Chrome instance:
We want to exchange files with the Chrome instance. Chrome will save the generated output files to an exchange folder, where our application can pick them up. This folder shall be the only writable folder for Chrome:
sudo mkdir /var/fileexchange
# Set folder ownership.
sudo chown chrome:chrome /var/fileexchange
# Set appropriate file exchange folder permissions.
sudo chmod 775 /var/fileexchange
For running our headless Chrome in the background, we create a system-wide systemd
ExecStart=/opt/chrome --headless --no-first-run --no-default-browser-check --no-sandbox --disable-setuid-sandbox --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --window-size="1920,1200" --force-device-scale-factor=0.5
Key aspects of this service unit:
Type=simple: There's only one main process, which is started with
Restart=always: Restart the service in case of any failures or interruptions.
RuntimeMaxSec=1d: Limit the service to a one day runtime. The service will automatically be stopped and because of above
DynamicUser=true: Hardens file and process access to the system. It implies private temporary folders and strict system protection.
User=chrome: The system user that will run the Chrome instance. We use our previously created non-elevated user, rather than a random user which
DynamicUser=truewould create without this directive.
ProtectSystem=strict: This option is redundant. It is implied already by
DynamicUser=true. We kept as reminder of a very strict read-only file system.
ReadWritePaths=/var/fileexchange: Space separated whitelist for writable folders. Adding our previously created file exchange folder here ensures Chrome can write to this folder.
Environment=HOME=/tmp: Set the home directory to the private temp files path.
Environment=DISPLAY=:0: Explicitly use the first display by defining the
ExecStart: The path to and start options for our headless Chrome instance. You may want to replace the path with your actual Chrome path and also adjust the Chrome startup flags according to your requirements.
WantedBy=multi-user.target: Start the service after all network services are available and when the system is ready for login.
Set proper file permissions for the systemd service file:
Reload the unit files to make systemd know about the new service:
Start the service and observe any error messages (e.g., when the path to Chrome isn't correct, the chosen remote debugging port already used, with a typo in Chrome startup flags, and so on):
If no errors occurred and the service is running fine, enable it for automatic start on machine boot:
We successfully created a systemd service for headless Chrome that
- restarts daily,
- starts with a non-elevated user,
- is mostly isolated from the remaining system, but
- can create files for processed output in a specifically defined location.