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
ExecStartkjører. - Kapittelet Tjenester og logger dekker grunnleggende
systemctlogjournalctlfor 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:
- systemd.service(5) — alle tjenestealternativene, fra Type= til Restart=
- systemd.service(5) på man.archlinux.org — samme manual, ren HTML
- ArchWiki: Systemd — utprøvde mønstre og det videre økosystemet