You run systemctl restart nginx and get back a wall of red: "Job for nginx.service failed because the control process exited with error code." That message tells you almost nothing. The real answer is sitting in the logs, and it takes about two minutes to find once you know where to look. This guide works the same way for Nginx, Apache, PHP-FPM, MariaDB, Redis, or any custom service on your SkyServer VPS.

The symptom

You'll typically see one of these after a restart, a reboot, or a config change:

  • Job for nginx.service failed because the control process exited with error code.
  • Failed to start mariadb.service: Unit mariadb.service has failed.
  • Active: failed (Result: exit-code) or activating (auto-restart) in systemctl status
  • start request repeated too quickly for php8.3-fpm.service
  • Unit app.service not found.

Step 1: Read the actual error

Don't guess. Run these two commands, in this order:

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

The -u flag filters to one unit, -e jumps to the end, and -x adds helpful explanations. The last few lines before the failure are almost always the cause. If you want only the current boot, add -b.

Step 2: Match the error to a cause

Here are the patterns we see most often on customer VPS servers:

What the log saysLikely causeFix
Address already in useAnother process holds the portss -tlnp | grep :80, then stop the other process
nginx: [emerg] ... in /etc/nginx/...Syntax error in confignginx -t shows the file and line
Permission deniedWrong file ownership or SELinuxCheck ls -l, then ausearch -m avc -ts recent
No such file or directoryMissing socket dir, cert, or binaryCreate the path or fix the config reference
Cannot allocate memory / KilledOOM killerAdd swap, lower memory settings
status=203/EXECThe ExecStart path is wrong or not executableFix the path, chmod +x
status=217/USERThe User= in the unit doesn't existCreate the user or correct the unit

Step 3: Test the config before restarting

Most services can validate their own config without starting. Make it a habit before every restart:

nginx -t
apachectl configtest
php-fpm8.3 -t
mariadbd --help --verbose >/dev/null

If a test fails, it prints the file and line number. Fix that, test again, and only then restart.

Step 4: Port conflicts, the classic one

A very common story: Apache got installed as a dependency, grabbed port 80, and now Nginx can't start. Find who owns the port:

ss -tlnp | grep -E ':80|:443'

The output shows the process name and PID. Stop and disable the unwanted one:

systemctl disable --now apache2

Step 5: Handle "start request repeated too quickly"

systemd gives up on a service that crashes over and over (by default 5 starts in 10 seconds). Once that limit is hit, even a fixed config won't start until you clear the counter:

systemctl reset-failed php8.3-fpm
systemctl start php8.3-fpm

Fix the root cause first, though. Resetting without fixing just burns another five attempts.

Step 6: Custom services and unit file mistakes

If you wrote your own unit (for a Node, Python or Go app), these are the usual slips:

  1. Edited the file in /etc/systemd/system/ but forgot systemctl daemon-reload.
  2. Used a relative path in ExecStart=. Always use absolute paths like /usr/bin/node.
  3. Missing WorkingDirectory=, so the app can't find its files.
  4. Environment variables set in your shell but not in the unit. Use Environment= or EnvironmentFile=.

A minimal working example:

[Unit]
Description=My app
After=network.target

[Service]
User=appuser
WorkingDirectory=/home/appuser/app
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Then run systemctl daemon-reload && systemctl enable --now app.

Step 7: Service works manually but fails at boot

If systemctl start works fine later but the service fails on reboot, it's usually an ordering problem. The service starts before the network, a mount, or the database is ready. Add the right dependencies to the unit:

After=network-online.target mariadb.service
Wants=network-online.target

Check what failed at boot with systemctl --failed and journalctl -b -p err.

Prevention

  • Run the built-in config test before every restart or reload. Prefer reload over restart where it's supported.
  • Keep a second SSH session open while you change critical services.
  • Use Restart=on-failure on your own units so a transient crash heals itself.
  • Watch memory. Many "random" failures are the OOM killer, and swap plus sensible limits prevents them.
  • Check systemctl --failed after every reboot or package update.

If you've read the journal and still can't pin it down, open a ticket with the output of systemctl status and the last 50 lines of journalctl -xeu for that service. Our team can usually spot it quickly from that.

Frequently Asked Questions

What's the difference between systemctl status and journalctl?

systemctl status shows current state plus only the last few log lines. journalctl -u gives you the full history for that service, which is where the real error usually lives.

Do I need daemon-reload after every change?

Only after you edit or create a unit file. Changing the service's own config (like an Nginx server block) just needs a restart or reload of that service.

Why does my service say "activating (auto-restart)" forever?

It's crashing right after start and Restart= keeps relaunching it. Read the journal for the crash reason, fix it, then run systemctl reset-failed if it hit the start limit.

Is it safe to use kill -9 on a stuck service?

Try systemctl stop first, since it shuts down cleanly. Use systemctl kill -s SIGKILL name only as a last resort, especially for databases, because it risks data recovery on the next start.

How do I see why a service failed during the last boot?

Run journalctl -b -1 -u servicename for the previous boot, or journalctl -b -p err for all errors in the current one.