panic: $HOME is not defined from an Ollama command inside a systemd service means the command tried to find the models directory through the HOME variable, and systemd starts services with no HOME at all. Set it in the unit with Environment="HOME=/usr/share/ollama" and the panic goes away.

The runtime's own ollama.service already sets HOME, which is why the server runs fine. The panic appears when you write a second unit that calls the ollama client, a warm-up script, a health check, a timer, and forget that your unit does not inherit anything from the first one.

The error

ollama-warm.sh[1197]: nomic-embed-text:latest  0a109f422b47  323 MB  100% GPU  2048  24 hours from now
ollama-warm.sh[1197]: panic: $HOME is not defined
systemd[1]: ollama-warm.service: Main process exited, code=exited, status=2/INVALIDARGUMENT
systemd[1]: ollama-warm.service: Failed with result 'exit-code'.

Why it happens

The ollama client resolves its model store under the user's home directory unless OLLAMA_MODELS says otherwise, and the code path that does so panics rather than guesses when HOME is unset. Interactive shells always have HOME, so the command works when you type it. systemd does not set HOME for a service unless the unit asks for it, so the same command run by a timer panics. Note the order in the log above: the warm-up's HTTP calls to the server had already loaded both models; only the final ollama ps for the log line failed. The unit was reported as failed while having done its job, which is the confusing part.

The fix

[Service]
Type=oneshot
# The ollama client resolves the model store from HOME; systemd sets none.
Environment="HOME=/usr/share/ollama"
ExecStart=/usr/local/bin/ollama-warm.sh

Use the runtime's own home, the one its installer created for the ollama system user, so both processes agree on where the models are. The alternative is to set OLLAMA_MODELS explicitly in both units; either works, but one line is enough. After editing, systemctl daemon-reload and start the unit once by hand to see it exit clean.

What the AI got wrong: it wrote the timer unit from the pattern of the runtime's unit without copying the environment lines, and trusted "enabled, active" from the timer as success. The journal said otherwise. A oneshot that ends in a panic after doing its work is the kind of failure a green status hides.

Where it bit us

Season four, Part 11: running it for real, installing the warm-up timer on the Ubuntu server. The models were loaded, the timer was scheduled, and the unit was red. One environment line and a daemon-reload later it was green, and the runbook carries the comment so the next unit does not repeat it.

Frequently asked

Why does ollama work in my shell but panic under systemd?
Your shell has HOME set; a systemd service does not unless its unit sets it. The ollama client reads HOME to find the model store and panics when it is missing. Add Environment=HOME=/usr/share/ollama to the unit.
Which HOME should a systemd unit use for the ollama client?
The runtime's own home, /usr/share/ollama, created by the installer for the ollama system user. That keeps the client and the server looking at the same models directory. Setting OLLAMA_MODELS in both units is the equivalent alternative.
Did the warm-up actually fail when the unit showed exit-code?
No. The HTTP requests that load the models had already succeeded; only the final ollama ps for the log panicked. Read the journal lines above the panic before assuming the job did nothing.

More decoded errors in the Fixes category, or start the season that produced this one at Part 1.