Cloud Explainers

How to Fix a Linux systemd Service That Won't Start

Linux10 min videoPublished Nov 9, 2026

Your systemd service won't start: systemctl start prints nothing, but the app isn't running. In our own lab (systemd 255 on Ubuntu 24.04) we break and fix the usual causes for real: 203/EXEC from a wrong program path, an edit that needs daemon-reload, an app that works by hand but can't see your shell's environment variable as a service, a port that's already taken, and 'start request repeated too quickly', which blocks your start even after the fix.

How to Fix a Linux systemd Service That Won't Start, the full picture: diagram of Ana's terminal, systemd, app.py, The journal, teashop.service, Port 8080
The whole picture, from the video's recap.

Started, but not running

You start your systemd service and get no error, but it isn't running. Nothing answers, and systemctl says failed. Ana's tea shop server is stuck right there, and her start command printed nothing at all.

shop: Ana's terminal
sudo systemctl start teashop
shop: Ana's terminal
curl -sS localhost:8080curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server
shop: Ana's terminal
systemctl is-active teashopfailed

When a service won't start, the reason is always written down. Two commands read it, and today Ana's shop hits the most common causes, one after another: a wrong program path, an app that runs fine by hand, and a port that's already taken. Along the way you'll meet a message that sounds like a crash: start request repeated too quickly. By the end you'll know what it really means.

You're in good company. On Unix and Linux Stack Exchange, the question about a new service failing with status 203/EXEC has over 360,000 views. That's exactly where Ana starts.

systemd "status=203/EXEC" error when creating new service: figure of 368,181 views on Unix & Linux Stack Exchange, source: api.stackexchange.com, question 472950, 2026-11-09

The usual fix: read the reason

The usual fix starts with reading, not guessing. Command one: systemctl status teashop. The two extra options only print it all at once, without log lines. The cross and the word failed say it's dead. And the Main PID line, the app's process ID, says how it ended: code exited, status 203/EXEC.

shop: Ana's terminal
systemctl status teashop --no-pager -n 0× teashop.service - Ana's tea shop
     Loaded: loaded (/etc/systemd/system/teashop.service; disabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2026-11-09 18:47:24 UTC; 850ms ago
   Duration: 1ms
    Process: 161 ExecStart=/usr/bin/python app.py (code=exited, status=203/EXEC)
   Main PID: 161 (code=exited, status=203/EXEC)
        CPU: 949us

Read that in three parts. Exited: the process ended by itself. 203: codes of 200 and up come from systemd itself, while it was still setting up, so the app never ran. And EXEC names the step that failed: running the program. systemd's manual says it's most likely a missing program file.

How the service ended, in three parts: diagram of code=exited, status=203/EXEC, exited = it ended by itself, 203 = systemd's own code, EXEC = the step that failed, 203/EXEC: the program in ExecStart can't be run

So Ana checks the program in her unit file: /usr/bin/python. But Ubuntu 24.04 only has python3. She fixes the line with sed, a tool that swaps text in a file: any editor, like nano, works too. And she checks again: python3.

shop: Ana's terminal
grep -n ExecStart /etc/systemd/system/teashop.service8:ExecStart=/usr/bin/python app.py
shop: Ana's terminal
ls /usr/bin/python*/usr/bin/python3  /usr/bin/python3.12
shop: Ana's terminal
sudo sed -i 's|/usr/bin/python |/usr/bin/python3 |' /etc/systemd/system/teashop.service
shop: Ana's terminal
grep -n ExecStart /etc/systemd/system/teashop.service8:ExecStart=/usr/bin/python3 app.py

Then she restarts the service, and looks at its status. Guess first: what happens now? Same error. systemctl warns that the unit file changed on disk, and status still shows 203, from running plain python: the old line, not the one she just fixed.

shop: Ana's terminal
sudo systemctl restart teashopWarning: The unit file, source configuration file or drop-ins of teashop.service changed on disk. Run 'systemctl daemon-reload' to reload units.
shop: Ana's terminal
systemctl status teashop --no-pager -n 0× teashop.service - Ana's tea shop
     Loaded: loaded (/etc/systemd/system/teashop.service; disabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2026-11-09 18:47:37 UTC; 872ms ago
   Duration: 1ms
    Process: 184 ExecStart=/usr/bin/python app.py (code=exited, status=203/EXEC)
   Main PID: 184 (code=exited, status=203/EXEC)
        CPU: 834us
Warning: The unit file, source configuration file or drop-ins of teashop.service changed on disk. Run 'systemctl daemon-reload' to reload units.

Why? systemd doesn't read unit files each time you start something. It loads them into memory, and keeps using that copy. Ana's file on disk says python3, but the copy in memory still says python. daemon-reload tells systemd to read every unit file again.

systemd runs its copy, not your file: diagram of Ana's edit, File on disk, systemd's copy, The app

So: daemon-reload, restart, and status: active, running, with python3 as its main process. The shop answers: 12 teas in stock. That's the usual fix: read the status, fix the file, reload, restart.

shop: Ana's terminal
sudo systemctl daemon-reload
shop: Ana's terminal
sudo systemctl restart teashop
shop: Ana's terminal
systemctl status teashop --no-pager -n 0● teashop.service - Ana's tea shop
     Loaded: loaded (/etc/systemd/system/teashop.service; disabled; preset: enabled)
     Active: active (running) since Mon 2026-11-09 18:47:49 UTC; 2s ago
   Main PID: 209 (python3)
      Tasks: 1 (limit: 9563)
     Memory: 8.8M (peak: 9.0M)
        CPU: 46ms
     CGroup: /system.slice/teashop.service
             └─209 /usr/bin/python3 app.py
shop: Ana's terminal
curl -s localhost:8080Ana's Tea Shop: 12 teas in stock

Why start printed nothing

So why did start print nothing at the very beginning? By default, a service is Type simple. systemd creates the new process, and counts the start as done right there, before the program has even tried to run. systemd's own manual warns about it: start reports success even when the program file is missing.

Type=simple: started means created: diagram of Ana, systemd, The app, start teashop, new process, done, can't run python, Start succeeds even if the program is missing

One line in the unit file changes that: Type=exec. Then systemd waits until the program really runs. We tried Ana's first draft with that one line added. Same wrong path, but now start fails out loud: job failed, with an exit status of 1.

Type=simple or Type=exec?: comparison table of Type=simple (default) vs Type=exec, Start is done, Wrong path, Exit status

What the exit code tells you

The exit code is your first clue. 203/EXEC: the program can't be run. 200/CHDIR: the WorkingDirectory folder doesn't exist. 217/USER: the User in the unit file doesn't exist. We caused each one on Ana's server. And a small number, like 1/FAILURE, means the app did start, then quit with an error of its own. For those, you need the app's own words.

Read the exit code first: cards of 203/EXEC, 200/CHDIR, 217/USER, 1/FAILURE, 200 and up: systemd failed before your app ran

Works by hand, fails as a service

Ana's next version of the app reads its stock file's path from an environment variable, TEA_STOCK. She set it in her bashrc, and her terminal has it. She stops the service, and tests the new app by hand. nohup and the ampersand at the end keep it running in the background, with its output in a log file. 12 teas. It works.

shop: Ana's terminal
cd teashop
shop: Ana's terminal
grep -n TEA_STOCK app.py5:# TEA_STOCK is set in Ana's ~/.bashrc
6:stock_file = os.environ["TEA_STOCK"]
shop: Ana's terminal
echo $TEA_STOCK/home/ana/teashop/stock.txt
shop: Ana's terminal
sudo systemctl stop teashop
shop: Ana's terminal
nohup python3 app.py > test.log 2>&1 &[1] 224
shop: Ana's terminal
curl -s localhost:8080Ana's Tea Shop: 12 teas in stock

Then she restarts the service on the new version. This time she runs status with sudo and without the dash n zero, so it also shows the last log lines. Failed again, but now with status 1/FAILURE: the app ran, and quit. And those last lines are all about restarting it. The reason itself scrolled away.

shop: Ana's terminal
sudo systemctl restart teashop
shop: Ana's terminal
sudo systemctl status teashop --no-pager× teashop.service - Ana's tea shop
     Loaded: loaded (/etc/systemd/system/teashop.service; disabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2026-11-09 18:47:54 UTC; 1s ago
   Duration: 46ms
    Process: 238 ExecStart=/usr/bin/python3 app.py (code=exited, status=1/FAILURE)
   Main PID: 238 (code=exited, status=1/FAILURE)
        CPU: 45ms

Nov 09 18:47:54 shop systemd[1]: teashop.service: Scheduled restart job, restart counter … at 5.
Nov 09 18:47:54 shop systemd[1]: teashop.service: Start request repeated too quickly.
Nov 09 18:47:54 shop systemd[1]: teashop.service: Failed with result 'exit-code'.
Nov 09 18:47:54 shop systemd[1]: Failed to start teashop.service - Ana's tea shop.
Hint: Some lines were ellipsized, use -l to show in full.

So keep the two commands straight. Status tells you the state, and how it ended. journalctl tells you why, in the app's own words: every line, not just the last ten.

Two commands, two questions: comparison table of systemctl status vs journalctl -u, Answers, Shows, Log lines

That's what command two is for: journalctl -u teashop, every line this service printed. Read up from the bottom, past the restart lines, to the first error: KeyError, TEA_STOCK. The app asked for the variable, and it wasn't there.

shop: Ana's terminal
sudo journalctl -u teashop --no-pager -n 14 -o catteashop.service: Scheduled restart job, restart counter is at 4.
Started teashop.service - Ana's tea shop.
Traceback (most recent call last):
  File "/home/ana/teashop/app.py", line 6, in <module>
    stock_file = os.environ["TEA_STOCK"]
                 ~~~~~~~~~~^^^^^^^^^^^^^
  File "<frozen os>", line 685, in __getitem__
KeyError: 'TEA_STOCK'
teashop.service: Main process exited, code=exited, status=1/FAILURE
teashop.service: Failed with result 'exit-code'.
teashop.service: Scheduled restart job, restart counter is at 5.
teashop.service: Start request repeated too quickly.
teashop.service: Failed with result 'exit-code'.
Failed to start teashop.service - Ana's tea shop.

How did the app's error get into the journal? A service has no terminal to print to. systemd catches everything the app prints, normal lines and errors, and files it in the journal under the unit's name. That's what dash u picks: the lines of one unit.

Where a service's output goes: diagram of app.py, systemd, The journal, Ana's terminal

But why was TEA_STOCK missing? Ana's terminal runs a shell that reads her bashrc when it starts, so every program she starts from it gets TEA_STOCK. A service doesn't come from her terminal. systemd starts it directly, with only what the unit file says. Her bashrc is never read.

Her terminal has the setting, the service doesn't: diagram of ~/.bashrc, Ana's terminal, App by hand, teashop.service, systemd, App as a service

The fix: put the setting in the unit file itself. One Environment line in the Service section, added here with sed, though any editor works. Then, as after every edit, daemon-reload, and restart. And it's failed again.

shop: Ana's terminal
sudo sed -i '/^WorkingDirectory=/a Environment=TEA_STOCK=/home/ana/teashop/stock.txt' /etc/systemd/system/teashop.service
shop: Ana's terminal
cat /etc/systemd/system/teashop.service[Unit]
Description=Ana's tea shop
After=network.target

[Service]
User=ana
WorkingDirectory=/home/ana/teashop
Environment=TEA_STOCK=/home/ana/teashop/stock.txt
ExecStart=/usr/bin/python3 app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
shop: Ana's terminal
sudo systemctl daemon-reload
shop: Ana's terminal
sudo systemctl restart teashop
shop: Ana's terminal
systemctl is-active teashopfailed

The port is already taken

Back to the journal, for the newest error. OSError: Address already in use. Something else already holds port 8080. And at the bottom, that message again: start request repeated too quickly.

shop: Ana's terminal
sudo journalctl -u teashop --no-pager -n 9 -o cat  File "/usr/lib/python3.12/socketserver.py", line 473, in server_bind
    self.socket.bind(self.server_address)
OSError: [Errno 98] Address already in use
teashop.service: Main process exited, code=exited, status=1/FAILURE
teashop.service: Failed with result 'exit-code'.
teashop.service: Scheduled restart job, restart counter is at 5.
teashop.service: Start request repeated too quickly.
teashop.service: Failed with result 'exit-code'.
Failed to start teashop.service - Ana's tea shop.

Who has the port? ss lists listening ports, and with sudo, the program behind each one: python3, process 224. ps says whose it is: Ana's, running app.py. It's her own test copy, the one she started by hand.

shop: Ana's terminal
sudo ss -ltnp 'sport = :8080'State      Recv-Q     Send-Q         Local Address:Port         Peer Address:Port    Process
LISTEN     0          5                    0.0.0.0:8080              0.0.0.0:*        users:(("python3",pid=224,fd=3))
shop: Ana's terminal
ps -o pid,user,etime,args -p 224    PID USER         ELAPSED COMMAND
    224 ana            00:17 python3 app.py

One port, one listener. Her test copy took 8080 first, so each time systemd started the service, it reached for the same port, got address already in use, and quit.

Start request repeated too quickly

So she stops her test copy with kill. Nothing holds the port now, and she starts the service right away. Guess first: does it start? No. Job failed. The Terminated line is only her shell, confirming the kill. And the journal gives just one reason, with no app error at all: start request repeated too quickly.

shop: Ana's terminal
kill 224
shop: Ana's terminal
sudo systemctl start teashopJob for teashop.service failed because the control process exited with error code.
See "systemctl status teashop.service" and "journalctl -xeu teashop.service" for details.
[1]+  Terminated              nohup python3 app.py > test.log 2>&1
shop: Ana's terminal
sudo journalctl -u teashop --no-pager -n 3 -o catteashop.service: Start request repeated too quickly.
teashop.service: Failed with result 'exit-code'.
Failed to start teashop.service - Ana's tea shop.

That message isn't a crash. It's a safety limit. With Restart on-failure, systemd started the app again after every crash. By default it allows five starts in ten seconds. One more, and it stops trying, so a broken service can't spin forever. And until those ten seconds are up, it refuses your start too.

Restart on failure, until the limit: diagram of Ana, systemd, The app, start, crash, starts 2 to 5, 5 starts in 10 seconds, then systemd stops

systemctl show prints the two limits: a burst of five starts, in ten seconds. So there are two ways out: wait the ten seconds, or run reset-failed, which makes systemd forget the failures. Ana resets, starts, and it's active, running. 12 teas, from the service this time.

shop: Ana's terminal
systemctl show teashop -p StartLimitBurst -p StartLimitIntervalUSecStartLimitIntervalUSec=10s
StartLimitBurst=5
shop: Ana's terminal
sudo systemctl reset-failed teashop
shop: Ana's terminal
sudo systemctl start teashop
shop: Ana's terminal
systemctl status teashop --no-pager -n 0● teashop.service - Ana's tea shop
     Loaded: loaded (/etc/systemd/system/teashop.service; disabled; preset: enabled)
     Active: active (running) since Mon 2026-11-09 18:48:09 UTC; 2s ago
   Main PID: 305 (python3)
      Tasks: 1 (limit: 9563)
     Memory: 8.6M (peak: 8.7M)
        CPU: 20ms
     CGroup: /system.slice/teashop.service
             └─305 /usr/bin/python3 app.py
shop: Ana's terminal
curl -s localhost:8080Ana's Tea Shop: 12 teas in stock

Catch it before you start

Some of this you can catch before you ever start. systemd-analyze verify reads a unit file and checks it. Ana's first draft: /usr/bin/python is not executable. A draft with a typo, ExecStar: an unknown key, so the service has no ExecStart at all, and systemd refuses it. A good file prints nothing, so for her final file an echo adds a line when the check passes: no problems found.

shop: Ana's terminal
cd ~/drafts
shop: Ana's terminal
systemd-analyze verify teashop.serviceteashop.service: Command /usr/bin/python is not executable: No such file or directory
shop: Ana's terminal
systemd-analyze verify teashop2.service/home/ana/drafts/teashop2.service:8: Unknown key name 'ExecStar' in section 'Service', ignoring.
teashop2.service: Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing.
Unit teashop2.service has a bad unit file setting.
shop: Ana's terminal
systemd-analyze verify /etc/systemd/system/teashop.service && echo no problems foundno problems found

verify reads the file, not the running machine. It catches a wrong program path, and typos in the keys, but it can't see a variable your terminal has, or a port somebody else is holding. Those still need the journal.

What verify can and can't catch: comparison table of verify catches vs only the journal shows, Program path, Typos, Missing setting, Busy port

Real software ships exactly this way. Elasticsearch, from Elastic, installs on Debian and Ubuntu as a systemd service, set up with daemon-reload and enable. And its docs warn about what fooled Ana at the start: the start command gives no feedback about whether it worked. You check the logs.

Elasticsearch: a systemd service too: cards of Installs as, Set up with, Start says, So you

Check these five

So when a service won't start, check these five. Status, for the exit code. journalctl -u, for the app's own words. After every edit, daemon-reload. Settings from your shell go in the unit file. And after a crash loop, reset-failed. Every command and the lab files are linked in the description, so you can break Ana's shop yourself.

When a service won't start: cards of 1. systemctl status, 2. journalctl -u, 3. daemon-reload, 4. Environment=, 5. reset-failed, Try it yourself

Recap

If this got your service running again, subscribe, and the next Linux fix comes to you.

Every error in this video happened for real, on our own lab server. Thanks for watching Cloud Explainers.

Sources

Demos: one Ubuntu server container with systemd, on our own machine; the tea shop is made up. Watch the full video on YouTube.

More videos

Linux9 min

Linux Makefiles Will Finally Make Sense After This

Auth (OAuth, JWT)9 min

SAML vs OAuth vs OpenID Connect: What's the Difference?

Python7 min

How to Fix Python cron Jobs That Never Run

Linux22 min

Linux Bash Scripting: Full Tutorial for Beginners

Networking10 min

How PXE Network Boot Works

Active Directory9 min

How Active Directory Works

Linux9 min

Linux Logs: syslog vs journald

Auth (OAuth, JWT)9 min

JWT vs Session Cookies: Which One Should You Use?

Docker10 min

Docker vs Docker Compose: Which One Should You Use?

Docker9 min

How to Fix Docker Compose Containers That Can't Talk to Each Other

Django (Python web framework)22 min

Django Full Tutorial for Beginners

Linux9 min

How Linux systemd Works

Networking8 min

How to Fix 'Windows Cannot Access' Network Share Errors (SMB)

APIs10 min

REST vs gRPC: Which One Should You Use?

Certificates & PKI (ADCS)9 min

How to Fix SSL Certificate Chain Errors (Missing Intermediate)

Python9 min

Every Python Concept for Beginners in 10 Minutes

Databases & SQL9 min

Every SQL Join Type in 10 Minutes

OpenRouter (one API for many AI models)9 min

Why OpenRouter Free Models Hit Their Limit So Fast

Gemini API & Google AI Studio9 min

Why Your Gemini API Free Tier Runs Out So Fast

OpenRouter (one API for many AI models)10 min

OpenRouter Will Finally Make Sense After This

Caching & Redis10 min

How Redis Caching Works

Gemini API & Google AI Studio10 min

Gemini API vs Vertex AI: Which One Should You Use?

Gemini API & Google AI Studio8 min

How to Fix a Leaked Gemini API Key

Go (Golang)9 min

Why Golang Uses Goroutines Instead of Threads

Gemini API & Google AI Studio9 min

How to Fix Gemini API Error 429: Resource Exhausted

Claude & the Claude Developer Platform10 min

Claude Token Pricing Will Finally Make Sense After This

Python11 min

Why Python Web Scraping Returns Nothing (and the Fix)

Python10 min

Python uv vs pip: Which One Should You Use?

Python22 min

Python Automation: Full Tutorial for Beginners

Auth (OAuth, JWT)9 min

How OAuth Login Works

Gemini API & Google AI Studio10 min

Google AI Studio Will Finally Make Sense After This

Docker10 min

What Happens When You Run a Docker Container

Claude & the Claude Developer Platform8 min

How to Fix Claude Code Billing Your API Key Instead of Your Plan

Auth (OAuth, JWT)9 min

Why Passkeys Can't Be Phished Like Passwords

PowerShell10 min

PowerShell vs Bash: Which One Should You Learn?

Linux10 min

The Linux Roadmap for Beginners

Django (Python web framework)9 min

Django vs FastAPI: Which One Should You Use?

Databases & SQL10 min

Why SQLite Is the Most Used Database in the World

Databases & SQL11 min

How a NoSQL Database Like MongoDB Works

Python11 min

How to Fix Python pip error: externally-managed-environment

Databases & SQL9 min

How to Fix MySQL Too Many Connections Database Errors

Go (Golang)9 min

Golang Concurrency Will Finally Make Sense After This

AWS10 min

How Amazon S3 Works

Kubernetes10 min

How Kubernetes Works

Git10 min

How Git Works

Docker10 min

How to Fix Docker Port Already Allocated Errors

PostgreSQL10 min

How PostgreSQL Works

Git8 min

How to Fix Git Merge Conflicts

Linux9 min

How to Fix No Space Left on Device in Linux

Networking10 min

How the Internet Works

Databases & SQL9 min

SQL vs NoSQL: When Each One Wins

Auth (OAuth, JWT)8 min

How to Fix JWT Token Expired Errors (401 Unauthorized)

Claude & the Claude Developer Platform9 min

Claude API vs Claude Pro: Why Your Subscription Doesn't Include It

AI agent skills9 min

Why AI Agent Skills Beat Giant Prompts

Linux10 min

Linux Cron Jobs Will Finally Make Sense After This

Cybersecurity10 min

Security Logs Will Finally Make Sense After This

Cybersecurity8 min

How to Fix a Security Leak: An API Key Pushed to GitHub

DNS9 min

DNS Will Finally Make Sense After This

Cybersecurity9 min

Never Ignore a Security Update

Auth (OAuth, JWT)9 min

Two-Factor Authentication Will Finally Make Sense

Networking8 min

Never Expose a Database to the Internet

Cybersecurity10 min

SSH Keys vs Passwords: Which Is Safer for Security?

Linux10 min

The 5 Levels of Linux Boot: From Power On to Login

Networking12 min

The 5 Levels of Network Firewalls

Cybersecurity10 min

Password Security: Why Hackers Crack Weak Passwords

Linux9 min

Why Your Linux Server Runs Out of Memory

Networking10 min

HTTP vs HTTPS: What Actually Changes on Your Network

Linux12 min

Every Linux File Permission You Need to Know

Linux10 min

Linux Processes vs Threads: When Each One Wins

Networking11 min

The OSI Model Will Finally Make Sense After This

Git11 min

The 7 Levels of Git

Networking10 min

Every Network Troubleshooting Command You Need

Cybersecurity10 min

Hashing vs Encryption in Cybersecurity: When Each One Wins

Networking11 min

Why the Internet Ran Out of IP Addresses

Linux11 min

Linux Package Managers: apt vs Snap vs Flatpak

Python9 min

Why Python Is Slow (and When It Doesn't Matter)

Networking11 min

TCP vs UDP: When Each One Wins

Linux8 min

Stop Running Linux as Root (Do This Instead)

Linux11 min

Linux Users and Groups Will Finally Make Sense

Cybersecurity9 min

Why Phishing Beats Good Security (and How to Spot It)

Docker12 min

The 7 Levels of Docker

AI agents & MCP12 min

AI Agents Will Finally Make Sense After This

Git8 min

Stop Using git push --force (Use This Instead)

AI agent skills10 min

Agent Skills vs MCP: When Each One Wins

Networking12 min

Networking Will Finally Make Sense After This

Linux12 min

Every Linux Command You Actually Need

Cybersecurity12 min

The 7 Levels of Cybersecurity

System design12 min

Every System Design Concept You Need, in One Diagram

PostgreSQL10 min

Why PostgreSQL Ignores Your Index