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)oractivating (auto-restart)insystemctl statusstart request repeated too quickly for php8.3-fpm.serviceUnit 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 says | Likely cause | Fix |
|---|---|---|
Address already in use | Another process holds the port | ss -tlnp | grep :80, then stop the other process |
nginx: [emerg] ... in /etc/nginx/... | Syntax error in config | nginx -t shows the file and line |
Permission denied | Wrong file ownership or SELinux | Check ls -l, then ausearch -m avc -ts recent |
No such file or directory | Missing socket dir, cert, or binary | Create the path or fix the config reference |
Cannot allocate memory / Killed | OOM killer | Add swap, lower memory settings |
status=203/EXEC | The ExecStart path is wrong or not executable | Fix the path, chmod +x |
status=217/USER | The User= in the unit doesn't exist | Create 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:
- Edited the file in
/etc/systemd/system/but forgotsystemctl daemon-reload. - Used a relative path in
ExecStart=. Always use absolute paths like/usr/bin/node. - Missing
WorkingDirectory=, so the app can't find its files. - Environment variables set in your shell but not in the unit. Use
Environment=orEnvironmentFile=.
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
reloadoverrestartwhere it's supported. - Keep a second SSH session open while you change critical services.
- Use
Restart=on-failureon 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 --failedafter 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.
