Home › Linux & shell

Job for X.service failed because the control process exited with error code — how to fix

Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

Quick fix: Run systemctl status X.service and journalctl -xeu X.service --no-pager | tail -50 to see the real error. Common causes: bad config (test with nginx -t, apachectl configtest, sshd -t), port already in use, permissions, or a wrong ExecStart path. Fix it, then sudo systemctl daemon-reload (if you edited the unit) and sudo systemctl restart X.

What it means

systemctl start (or restart, or apt starting a newly installed package) asked systemd to run the unit. The process systemd launched — the "control process" — exited with an error, so the job failed. systemd doesn't know why; the program printed its reason to its log. Your job is to find that line.

Step 1: read the logs

systemctl status nginx.service --no-pager -l
journalctl -xeu nginx.service --no-pager | tail -50

In status look at the lines:

Process: 1234 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)

That tells you which command failed (ExecStartPre here — the config test) and the exit status. The journal lines just above it contain the program's own message, e.g. nginx: [emerg] unknown directive "servr_name" in /etc/nginx/sites-enabled/default:3.

If the journal is empty, run the ExecStart command by hand as the service user:

systemctl cat myapp.service          # see ExecStart, User, WorkingDirectory
sudo -u myapp /opt/myapp/bin/myapp --config /etc/myapp.yml

Cause 1: configuration syntax error

The most common cause after editing a config file. Most daemons have a test mode:

sudo nginx -t
sudo apachectl configtest        # or: apache2ctl -t
sudo sshd -t
sudo named-checkconf
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo php-fpm8.3 -t
sudo visudo -c

For YAML or JSON configs (netplan, /etc/docker/daemon.json, app configs) a single bad indent or trailing comma stops the service — check it with the YAML validator or JSON formatter.

Cause 2: port already in use

Journal shows bind() to 0.0.0.0:80 failed (98: Address already in use) or similar:

sudo ss -ltnp | grep ':80 '

Stop the other program (often apache2 vs nginx, or an old instance not managed by systemd), or change the port. See Address already in use.

Cause 3: status=203/EXEC — the binary can't be executed

(code=exited, status=203/EXEC) means systemd couldn't run ExecStart at all: the path is wrong, it isn't executable, or a script lacks a shebang. ExecStart needs an absolute path:

[Service]
ExecStart=/usr/bin/node /opt/app/server.js
ls -l /opt/app/start.sh && sudo chmod +x /opt/app/start.sh
head -1 /opt/app/start.sh            # must be e.g. #!/bin/bash

Other numeric codes: 200/CHDIR (WorkingDirectory missing), 217/USER (User= doesn't exist), 226/NAMESPACE (a path in ReadWritePaths= etc. doesn't exist).

Cause 4: permissions

The service runs as a dedicated user (User= in the unit) that can't read its config, write its PID file, log directory or data directory:

sudo -u www-data test -r /etc/myapp/config.yml && echo ok
sudo chown -R mysql:mysql /var/lib/mysql

Also check SELinux/AppArmor denials: sudo journalctl -k | grep -i -e denied -e apparmor or sudo ausearch -m avc -ts recent.

Cause 5: you edited the unit file but didn't reload

sudo systemctl daemon-reload
sudo systemctl restart myapp

Prefer overrides to editing vendor units: sudo systemctl edit myapp creates /etc/systemd/system/myapp.service.d/override.conf.

Cause 6: Type= doesn't match how the program runs

If the program forks into the background but the unit says Type=simple, or vice versa with Type=forking, systemd may consider it failed or kill it. For most modern programs, run in the foreground and use Type=simple (or exec).

Cause 7: start-limit-hit

After repeated failures: Start request repeated too quickly. Fix the underlying error, then:

sudo systemctl reset-failed myapp
sudo systemctl start myapp

Useful afterwards

systemctl is-active myapp
journalctl -u myapp -f            # follow logs live
systemctl --failed                # list all failed units

Last updated 2026-11-06.