På denne siden

Guiden systemd-timere tok for seg jobber som kjører på klokka. Dette er den andre halvdelen av paret: tjeneste-unitten, den lille tekstfilen som gjør et skript til noe systemd starter, overvåker og logger.

Alt her er sjekket på Ubuntu 26.04, Fedora 44 og Arch i oktober 2026, på tvers av systemd 259 og 261. Noen journallinjer endrer ordlyd mellom utgivelser, og de stedene er flagget etter hvert som de dukker opp.

Eksemplene oppretter unit-filer under /etc/systemd/system/, skript under /usr/local/bin/ og en engangsbruker, så kjør dem på en maskin der det er velkomment.

Hva en unit egentlig er

En unit er en liten tekstfil som beskriver én ting systemd styrer. Tjeneste-units ender på .service og bruker tre seksjoner: [Unit] bærer beskrivelsen, [Service] sier hva som skal kjøres, og [Install] er oppstartskoblingen som systemctl enable virker på. En unit uten [Install] er helt lovlig.

Unit-filer ligger to steder:

  • /etc/systemd/system/ — dine. Alle units i denne guiden havner der.
  • /usr/lib/systemd/system/ — distribusjonens, hundrevis av dem. Les dem som eksempler. Rediger aldri noe der: pakkeoppdateringer overskriver endringene dine.

Suffiksen er valgfri på kommandolinjen: systemctl start hello og systemctl start hello.service er samme kommando, fordi systemd antar .service for et navn uten punktum. På disk holder du deg til hele navnet.

Den minste tjenesten som kan kjøre

Et skript først:

#!/bin/bash
# /usr/local/bin/run.sh
echo "hello at $(date +%T)"

Gjør det kjørbart med sudo chmod +x /usr/local/bin/run.sh, og gi det en unit i /etc/systemd/system/hello.service:

[Unit]
Description=Say hello

[Service]
Type=oneshot
ExecStart=/usr/local/bin/run.sh

Type=oneshot betyr at tjenesten kjører én gang og avslutter. Så reload, start og les beviset i journalen:

$ sudo systemctl daemon-reload
$ sudo systemctl start hello.service
$ journalctl -u hello.service
Oct 05 10:02:11 linuz-ubuntu run.sh[3512]: hello at 10:02:11

(PID-en i run.sh[3512] er fra eksempelet; din blir en annen.)

Alt skriptet skriver til stdout eller stderr havner i journalen, og journalctl -u NAME filtrerer på unit. Det er bevisteknikken for hele guiden, og grunnen til at hvert eksempelskript ender med en echo.

Det første daemon-reload-kallet var ritual, ikke nødvendighet. En helt ny unit starter fint uten reload, fordi systemd aldri har sett filen. Å redigere en unit den alt har i minnet, aktiv eller feilet, er annerledes: den gamle versjonen fortsetter å kjøre, og endringene dine ligger brakk til neste reload. Hopper du over den, advarer neste kommando på unitten deg:

$ sudo systemctl start hello.service
Warning: The unit file, source configuration file or drop-ins of hello.service changed on disk. Run 'systemctl daemon-reload' to reload units.

Advarselen betyr at fila på disk og uniten i minnet er uenige, og den navngir kuren:

$ sudo systemctl daemon-reload

Gjør daemon-reload til det faste steget etter hver redigering, så skjer en hel familie av mysterier av typen «hvorfor hjalp ikke fiksen min» aldri.

Hvordan systemd avgjør at du «kjører»

For Type=oneshot er svaret: bare mens skriptet kjører. En ferdigkjørt oneshot rapporterer Active: inactive (dead), noe som skremmer folk første gangen de ser det. Tjenesten fungerte. Den kjører bare ikke lenger. RemainAfterExit=yes i [Service]-seksjonen endrer det til Active: active (exited) etter en vellykket kjøring, som passer bedre for oppsettsskript der effekten bør leses som en tilstand i stedet for et blunk.

Langtidskjørende tjenester er den andre typen. En løkke viser hvordan de ser ut, og dette skriptet blir med gjennom resten av guiden:

#!/bin/bash
# /usr/local/bin/watcher.sh
while true; do
    echo "tick at $(date +%T)"
    sleep 10
done
# /etc/systemd/system/watcher.service
[Unit]
Description=Heartbeat ticker

[Service]
ExecStart=/usr/local/bin/watcher.sh

Gjør det kjørbart og start det:

$ sudo chmod +x /usr/local/bin/watcher.sh
$ sudo systemctl start watcher.service

Kjør journalctl -f -u watcher.service i en annen terminal (eller en tmux-rute), og tikkene kommer inn live.

Ingen Type=-linje, så systemd bruker Type=simple som standard: tjenesten regnes som startet i det øyeblikket ExecStart-prosessen finnes. En eksplisitt Type=simple oppfører seg identisk. systemctl status watcher.service viser, blant andre linjer:

Loaded: loaded (/etc/systemd/system/watcher.service; static)
   Active: active (running)
   Main PID: 831 (watcher.sh)

Nøyaktig formatering varierer mellom systemd-versjoner. Substansen gjør det ikke: Main PID er PID-en til prosessen systemd startet, nummeret avslutningsøvelsen dreper med vilje. Loaded:-linjen viser unitens filsti, og static der betyr at [Install]-seksjonen ennå mangler. Vi kommer tilbake til det når guiden kobler units inn i oppstarten.

Når den dør

Restart=on-failure starter tjenesten på nytt når prosessen dør uventet. Legg det til i [Service]-seksjonen i /etc/systemd/system/watcher.service, kjør så en reload og en restart, slik at den kjørende prosessen får med seg den nye policyen:

$ sudo systemctl daemon-reload
$ sudo systemctl restart watcher.service

Testen er å drepe Main PID for hånd (ta PID-en fra din egen systemctl status; 831 var tallet i eksempelet over):

$ sudo kill -9 831

Journalen skriver ut denne sekvensen, identisk på systemd 259 og 261:

watcher.service: Main process exited, code=killed, status=9/KILL
watcher.service: Failed with result 'signal'.
watcher.service: Scheduled restart job, restart counter is at 1.

Så starter systemd unitten igjen, under en fersk Main PID som status viser. «Started»-linjen som følger er ordlagt ulikt mellom utgivelser, så den siteres ikke her. Restart-telleren klatrer for hvert dødsfall, en rask helsesjekk for en tjeneste som stadig dør.

Restart=always går ett steg lenger: den starter på nytt selv etter en ren avslutning. Rene avslutninger logger Deactivated successfully. i journalen, lett å skille fra et krasj. RestartSec=2 venter rundt to sekunder mellom avslutning og omstart, og gapet er synlig i tidsstemplene i journalen. Les slike verdier som omtrent N sekunder, aldri som en garanti.

Ett unntak dekker alt dette: systemctl stop vinner. Å stanse en Restart=always-tjeneste holder den stanset. Policyen styrer dødsfall, ikke stans med vilje.

Én måte å dø uten å ha levd på: et skript uten execute-bit. For å gjenskape det, fjern biten igjen: sudo chmod -x /usr/local/bin/run.sh. Startforsøket skriver ut en første linje som er stabil på tvers av versjoner:

$ sudo systemctl start hello.service
Job for hello.service failed because the control process exited with error code.

Journalen sier navnet på forbrytelsen, og statusen er 203/EXEC:

Failed at step EXEC spawning /usr/local/bin/run.sh: Permission denied

sudo chmod +x på skriptet fikser det, og så starter du på nytt.

Uten Restart= er døden permanent. En feilet tjeneste forblir feilet og prøves aldri igjen. systemctl --failed lister opp unitten, merket failed, og avslutter utdataene med:

1 loaded units listed.

sudo systemctl reset-failed hello.service fjerner unitten fra den lista når den er håndtert, og tallet synker til 0 loaded units listed.

Hvem den kjører som

User= bestemmer kontoen: sett den, og prosessen kjører som den brukeren, med brukerens rettigheter. Et probeskript gjør beviset synlig, ett svar per linje:

#!/bin/bash
# /usr/local/bin/probe.sh
echo "user: $(whoami)"
echo "cwd: $PWD"
echo "greeting: $GREETING"
echo "extra: $SECOND_GREETING"

Tre oppsettskommandoer kommer først. -m i useradd -m lager hjemmemappen som WorkingDirectory= peker på:

$ sudo useradd -m probe
$ echo 'SECOND_GREETING=evening' | sudo tee /etc/probe.env
$ sudo chmod +x /usr/local/bin/probe.sh
# /etc/systemd/system/probe.service
[Unit]
Description=Report identity and environment

[Service]
Type=oneshot
User=probe
WorkingDirectory=/home/probe
Environment=GREETING=morning
EnvironmentFile=/etc/probe.env
ExecStart=/usr/local/bin/probe.sh

/etc/probe.env er en vanlig tekstfil med én linje, SECOND_GREETING=evening. Start unitten, og journalen svarer på alle fire spørsmålene: kontoen fra User=, mappen fra WorkingDirectory=, variabelen fra Environment= og den som bare kommer fra filen.

WorkingDirectory= setter prosessens gjeldende mappe, og når linjen mangler er den mappen /, noe som har ødelagt mange skript som skrev utdataene sine til en relativ sti. Environment= tar KEY=value-par rett i unitten. EnvironmentFile= leser dem fra en fil i stedet, ett par per linje, praktisk for innstillinger du justerer uten å røre unitten.

En User= som navngir en konto som ikke finnes, feiler med status 217/USER, og systemd-analyze verify fanger det ikke opp. Navnet er syntaktisk greit, men matcher ingen. Journalen er eksplisitt:

Failed to determine credentials for user 'nosuchuser': Unknown user
Failed at step USER spawning /usr/local/bin/probe.sh: Invalid argument

Overleve omstarten

Alt så langt dør ved omstart. systemctl start kjører en unit bare for gjeldende oppstart, og etter et strømbrudd har systemd glemt den. [Install]-seksjonen er fiksen: WantedBy=multi-user.target angir targeten alle normale oppstarter når, og enable kobler unitten inn i den. Legg den til i watcher.service, kjør en reload og enable:

# add to the end of /etc/systemd/system/watcher.service
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable watcher.service
Created symlink '/etc/systemd/system/multi-user.target.wants/watcher.service' → '/etc/systemd/system/watcher.service'.

En enkelt symlenke i multi-user.target.wants/, og det er slik systemd vet at unitten skal startes ved oppstart. Det er hele mekanismen. systemctl is-enabled watcher.service svarer nå enabled, og disable fjerner symlenken igjen:

$ sudo systemctl disable watcher.service
Removed '/etc/systemd/system/multi-user.target.wants/watcher.service'.

Ingen pil i disable-utdataene, siden ingenting pekes på, bare slettes. Verdt å pugge: disable stopper ikke en kjørende unit. Den rører bare oppstartskoblingen, så en tjeneste som står oppe, blir oppe til du stopper den.

For en helt ny tjeneste gjør én kommando begge deler: systemctl enable --now watcher.service aktiverer og starter i samme slengen.

Static-units fullender static-historien. En unit uten [Install]-seksjon kan ikke aktiveres, men dagens systemd gir ingen feil når du prøver. Den skriver ut en kort blokk som forklarer at det ikke finnes noe å aktivere, endrer ingenting på disk, og systemctl is-enabled fortsetter å svare static. Unitten er ikke ødelagt. Den bød seg bare aldri til oppstarten.

Endre en unit trygt

Redigeringssyklusen endrer seg aldri: rør filen, sudo systemctl daemon-reload, og restart. En advarsel tidligere i guiden bygde opp det ritualet, og å fjerne en unit viser det fra en annen side. hello spiller offeret, og den må kjøre når filen dens forsvinner: en inaktiv unit kan lese disken på nytt i det stille og hoppe fullstendig over bad-fasen, en aktiv kan ikke det. Start den, slett filen, og spør status før reload:

$ sudo systemctl start hello
$ sudo rm /etc/systemd/system/hello.service
$ systemctl status hello.service

status kjenner fortsatt unitten: Loaded:-linjen inneholder bad, med advarselen om endring på disk fra tidligere på slep:

Warning: The unit file, source configuration file or drop-ins of hello.service changed on disk. Run 'systemctl daemon-reload' to reload units.

Reloaden får systemd til å glemme:

$ sudo systemctl daemon-reload
$ systemctl status hello.service
Loaded: not-found (Reason: Unit hello.service not found.)

En siste stop finner ingenting igjen å stanse:

$ sudo systemctl stop hello
Unit hello.service could not be found.

En drop-in endrer en unit uten å redigere den. Lag en mappe oppkalt etter unitten pluss .d, og legg en fil med bare de endrede nøklene der. Her får watcher.service Restart=always, mens originalen forblir urørt:

# /etc/systemd/system/watcher.service.d/override.conf
[Service]
Restart=always

systemctl cat watcher.service viser det systemd faktisk leser: originalfragmentet først, deretter hver drop-in med sin egen # /path-topptekst. Der en nøkkel finnes i begge, vinner drop-in-en, så denne tjenesten restartes nå selv etter rene avslutninger.

Å angre det er å slette drop-in-mappen pluss en reload. For det meste. Én verifisert fallgruve: en slettet drop-in kan fortsatt virke for en kjørende unit til daemon-reload er kjørt, fordi prosessen fortsetter å leve under den gamle sammenslåtte konfigurasjonen. Enda en oppføring i ritualet etter hver redigering.

Det finnes en interaktiv snarvei, systemctl edit watcher.service, som åpner et redigeringsprogram og lagrer nøyaktig en slik override.conf. Den manuelle veien får anbefalingen her: en fil du har sett, er en fil du kan stole på.

En watcher som nekter å forbli død

Alt dette havner i én unit nå. watcher.sh beholder formen fra første gangs opptreden, med echo-linjen utvidet til å skrive ut brukeren og den konfigurerte variabelen:

#!/bin/bash
# /usr/local/bin/watcher.sh
while true; do
    echo "tick at $(date +%T) as $(whoami), greeting=$GREETING"
    sleep 10
done
# /etc/systemd/system/watcher.service
[Unit]
Description=Heartbeat watcher

[Service]
Type=simple
User=probe
Environment=GREETING=morning
EnvironmentFile=/etc/probe.env
WorkingDirectory=/home/probe
ExecStart=/usr/local/bin/watcher.sh
Restart=on-failure
RestartSec=2

[Install]
WantedBy=multi-user.target

Brukeren og env-filen kommer rett fra proben i forrige seksjon. Sjekk unitten før systemd ser den i det hele tatt:

$ systemd-analyze verify /etc/systemd/system/watcher.service

Stillhet er det sunne svaret: en ren unit skriver ut ingenting og avslutter med 0. Så selve igangkjøringen, og rekkefølgen betyr noe. Drop-in-en fra forrige seksjon setter fortsatt Restart=always, som i stillhet ville rangert over Restart=on-failure deklarert ovenfor, og watcher har kjørt siden den første starten: å starte en allerede kjørende unit tar aldri i bruk den nye filen. Derfor tas drop-in-en bort først, deretter kommer enable, og én restart flytter den kjørende unitten over på den nye konfigurasjonen. Enable-halvdelen får sin egen kommando denne gangen, fordi disable-demoen i Overleve omstarten tok bort oppstartssymlenken:

$ sudo rm -r /etc/systemd/system/watcher.service.d
$ sudo systemctl daemon-reload
$ sudo systemctl enable watcher.service
Created symlink '/etc/systemd/system/multi-user.target.wants/watcher.service' → '/etc/systemd/system/watcher.service'.
$ sudo systemctl restart watcher.service

Created symlink-linjen er den forventede. Journalen beviser at identiteten og miljøet slo an: hjerteslag-linjer med as probe og greeting=morning.

Ta Main PID fra systemctl status watcher.service, her 1247 (din blir en annen), og drep den:

$ sudo kill -9 1247
watcher.service: Main process exited, code=killed, status=9/KILL
watcher.service: Failed with result 'signal'.
watcher.service: Scheduled restart job, restart counter is at 1.

Rundt to sekunder senere, med RestartSec=2 i arbeid, kunngjør en Started-linje en ny kjøring, og hjerteslagene tar til igjen under en fersk Main PID. To sekunder med vilje, så beviset ikke lar deg vente. De tre journallinjene er den samme stabile sekvensen som i Når den dør, med ordlyd identisk på systemd 259 og 261.

Et ord til om systemd-analyze verify, fordi exit-kodene ikke er det folk forventer. Et direktiv den ikke klarer å tolke gir en sti:linje-diagnostikk, men kommandoen avslutter likevel med 0. Det er en advarsel:

/etc/systemd/system/hello.service:5: Failed to parse Type=nonsense, ignoring: Invalid argument

Exit code 1 er reservert for fatale problemer, en binærfil som ikke finnes, den klassen av feil som viser seg ved start som 203/EXEC-feilen fra Når den dør. Null betyr at ingenting var fatal, ikke at alle direktivene ga mening.

Nedrivningen: igangkjøringen baklengs, pluss alle de andre stiene denne guiden opprettet:

$ sudo systemctl stop watcher.service
$ sudo systemctl disable watcher.service
$ sudo rm /etc/systemd/system/watcher.service /usr/local/bin/watcher.sh
$ sudo rm /etc/systemd/system/hello.service /usr/local/bin/run.sh
$ sudo rm /etc/systemd/system/probe.service /usr/local/bin/probe.sh
$ sudo rm /etc/probe.env
$ sudo userdel -r probe
$ sudo systemctl daemon-reload
$ sudo systemctl reset-failed

Alle stier denne guiden opprettet, ingenting annet. Ligger det noe eget på en av disse stiene fra før, eller finnes det en probe-bruker fra tidligere eksperimenter, hopper du over de aktuelle linjene. -r i userdel -r tar hjemmemappen til probe med. systemctl --failed svarer med 0 loaded units listed., og maskinen er tilbake der den startet.

Hvor du står nå

Hele loopen: skriv en unit, kjør den én gang eller for alltid, restart den ved feil, kjør den som en bestemt bruker med kontrollert miljø, koble den inn i oppstarten og endre den trygt gjennom drop-ins. Journalen avgjør hva som faktisk skjedde, og daemon-reload er refleksen mellom redigering og forventning.

Veien videre:

  • Guiden systemd-timere kjører jobber etter en tidsplan i stedet for å restarte dem.
  • Guiden Feilsøking tar over når en unit oppfører seg dårlig og journalen ikke strekker til.
  • Guiden Bash — skript som holder mål er den andre halvdelen av hver tjeneste, skriptet ExecStart kjører.
  • Kapittelet Tjenester og logger dekker grunnleggende systemctl og journalctl for units som følger med distribusjonen din.

Ett hjørne er bevisst latt alene: units per bruker under systemctl --user, samme idé forankret i din egen innloggingssesjon i stedet for systemoppstarten. De fortjener en guide for seg selv.

For den komplette alternativreferansen er de offisielle sidene de du skal ha liggende åpne: