at ist Linux’ Werkzeug für einmalige zeitgesteuerte Jobs. Während cron für wiederkehrende Aufgaben gedacht ist — jede Stunde, jeden Montag, jeden Ersten im Monat — passt at immer dann, wenn ein Befehl genau einmal zu einem bestimmten Zeitpunkt laufen soll. „In zwei Stunden”, „heute Nacht um drei”, „morgen früh”, „nächsten Montag um zehn” — die Syntax ist bewusst nah an natürlicher Sprache. Der zugehörige Daemon heißt atd und ist auf vielen Distributionen standardmäßig deaktiviert; ohne ihn passiert nichts.
Was at macht
Der Befehl at registriert einen Job, der zu einem festen Zeitpunkt genau einmal ausgeführt wird. Die Spool-Verwaltung übernimmt der Daemon atd, der im Hintergrund läuft, die Job-Queue im Auge behält und fällige Jobs zur passenden Zeit startet. Anders als bei cron gibt es keine Wiederholung — wenn der Job einmal gelaufen ist, ist er weg.
Die Aufteilung zwischen cron und at ist klassisch: cron für „jeden Tag um 02:00”, at für „heute Nacht um 02:00”. Wer einmalige Wartungsfenster, geplante Restarts, terminierte Reminder oder Skripte mit Verzögerung braucht, ist mit at schneller am Ziel als mit einem extra Cron-Eintrag, der nach einmaliger Ausführung wieder entfernt werden müsste. Für komplexere wiederkehrende Aufgaben mit Logging, Abhängigkeiten und Retry-Logik sind systemd-Timer die modernere Wahl — at bleibt aber das Mittel der Wahl, wenn es schnell und unkompliziert sein soll.
Installation und Daemon
Auf vielen Distributionen ist at nicht vorinstalliert oder der Daemon ist nicht aktiv. Auf Debian und Ubuntu installiert apt install at Paket samt Daemon, danach muss atd einmal aktiviert werden, damit Jobs auch tatsächlich ausgeführt werden.
sudo apt install at
sudo systemctl enable --now atd
systemctl status atdAuf Fedora heißt das Paket ebenfalls at (dnf install at), auf Arch pacman -S at, auf Alpine apk add at. Ohne laufenden atd registriert at zwar Jobs in der Queue (/var/spool/atd/ oder ähnlich), aber niemand führt sie aus — eine häufige Stolperfalle. systemctl status atd zeigt schnell, ob der Daemon läuft. Der Daemon prüft typischerweise jede Minute, ob ein Job fällig ist.
Zeitangabe-Syntax
at versteht eine bewusst menschenlesbare Zeitsyntax — die meisten natürlichsprachlichen Formulierungen funktionieren direkt. Daneben gibt es das strukturierte Format -t YYYYMMDDhhmm für Skripte, in denen die Eingabe garantiert eindeutig sein muss.
| Beispiel | Bedeutung |
|---|---|
now + 1 hour | In einer Stunde ab jetzt |
now + 30 minutes | In 30 Minuten |
now + 2 days | In zwei Tagen, gleiche Uhrzeit |
18:00 | Heute um 18:00 Uhr (oder morgen, falls bereits vorbei) |
2pm tomorrow | Morgen um 14:00 Uhr |
tomorrow | Morgen, gleiche Uhrzeit wie jetzt |
next monday | Nächster Montag, gleiche Uhrzeit |
noon | Heute um 12:00 Uhr |
midnight | Heute um 00:00 Uhr (kommende Mitternacht) |
teatime | 16:00 Uhr — britischer Erbsensch*rz, aber gültig |
04:30 + 3 days | In drei Tagen um 04:30 Uhr |
-t 202605051830 | 5. Mai 2026, 18:30 Uhr — strukturiert |
Die Reihenfolge ist flexibel: at 14:00 tomorrow und at tomorrow 14:00 führen zum gleichen Ergebnis. Bei mehrdeutigen Eingaben gilt Vorwärts-Auflösung — at 09:00 heute Vormittag um zehn Uhr eingegeben bedeutet morgen um 09:00, nicht heute um 09:00. Wer es absolut eindeutig will, nimmt -t 202605060900 für den 6. Mai 2026 um 09:00 Uhr.
Job-Eingabe
Der klassische Aufruf at TIME öffnet einen kleinen Prompt, in den du die zu planenden Befehle Zeile für Zeile eintippst. Mit Strg+D (EOF) schließt du die Eingabe ab und at registriert den Job. Praktisch für mehrere Befehle hintereinander, in der Praxis aber meist überflüssig — One-Liner gehen schneller.
# 1) Interaktiv mit Prompt
at 18:00
# at> /home/me/backup.sh
# at> echo "fertig" | mail -s Backup me@example.org
# at> <Strg+D>
# 2) One-Liner mit Here-String
at 18:00 <<< '/home/me/backup.sh'
# 3) Skriptdatei einlesen
at 18:00 -f /home/me/backup.shwarning: commands will be executed using /bin/sh
job 7 at Tue May 5 18:00:00 2026Wichtig ist die Warnung in der Ausgabe: at führt die Befehle in /bin/sh aus, nicht zwangsläufig in deiner Login-Shell. Wer Bash-spezifische Features braucht, ruft bash -c '...' aus dem Job auf oder packt alles in ein Skript mit korrektem Shebang und übergibt es per -f. Die Job-ID in der zweiten Zeile (job 7) ist später für atrm und at -c relevant.
Job-Verwaltung
Drei Werkzeuge reichen für den Alltag: atq listet wartende Jobs, atrm entfernt sie, at -c JOBID zeigt den vollständigen Inhalt inklusive eingefrorener Umgebungsvariablen.
atq
at -c 7
atrm 77 Tue May 5 18:00:00 2026 a me
9 Wed May 6 02:00:00 2026 a meDie atq-Ausgabe zeigt Job-ID, geplante Zeit, Queue-Buchstaben (a ist Standard, b ist batch) und den Besitzer. Als normaler User siehst du nur deine eigenen Jobs, als root alle Jobs aller User. at -c 7 druckt das komplette Job-Skript — inklusive der gesamten gesetzten Umgebung zum Erstellungszeitpunkt, was schnell mehrere Hundert Zeilen sein können. atrm 7 9 entfernt mehrere Jobs auf einmal. Ein bereits gestarteter Job lässt sich nicht mehr per atrm stoppen — dafür brauchst du kill.
Praxis-Patterns
Die folgenden Bausteine decken die häufigsten Einsatzszenarien ab — vom kurzen Reminder bis zum geplanten Service-Restart.
Reminder in 30 Minuten
echo 'notify-send "Pause vorbei!"' | at now + 30 minutesKlassisches „erinnere mich”-Pattern. Der Befehl pipt das Echo in at, der eine ID ausgibt und den Job in die Queue legt. Damit notify-send auf dem Desktop tatsächlich auftaucht, muss die DBUS_SESSION_BUS_ADDRESS zum Erstellungszeitpunkt gesetzt sein — at snapshotted das Environment, also funktioniert es nur, wenn du den Befehl aus einer aktiven Desktop-Session abschickst.
Backup heute Nacht
echo '/home/me/backup.sh' | at 02:00Wenn 02:00 heute schon vorbei ist, plant at automatisch auf morgen um 02:00. Das Skript läuft dann genau einmal, schreibt sein Log selbst (oder bekommt seine Ausgabe per Mail zugestellt) und ist danach aus der Queue verschwunden. Für regelmäßige nächtliche Backups ist cron die richtige Wahl — für ein einmaliges Vor-Update-Backup genau das hier.
Server-Restart geplant
echo 'systemctl restart nginx' | sudo at 03:00 tomorrowWartungsfenster außerhalb der Geschäftszeiten ohne mitten in der Nacht aufzustehen. Wichtig ist sudo — sonst läuft der Job als dein normaler User und systemctl restart schlägt fehl. Die Job-Queue für root liegt getrennt von der User-Queue; sudo atq zeigt root-Jobs, ohne sudo siehst du sie nicht.
Verzögerte Mail aus Skript
at 17:00 -f /home/me/end-of-day-mail.sh-f liest den Job-Inhalt aus einer Datei statt von stdin. Die Datei muss kein ausführbares Skript sein — at liest sie als Befehlsliste und führt sie zur Trigger-Zeit in /bin/sh aus. Praktisch, wenn der Job mehrere Zeilen umfasst oder regelmäßig ähnlich angelegt wird (Vorlage versionieren, dann pro Tag mit at einplanen).
Alle Jobs einsehen mit Inhalt
atq
for id in $(atq | awk '{print $1}'); do
echo "=== Job $id ==="
at -c "$id" | tail -20
doneVor einer langen Wartung sinnvoll: kurz prüfen, was alles in der Queue liegt und was die Jobs konkret tun. Die Schleife ruft für jede Job-ID at -c auf und zeigt die letzten 20 Zeilen — typischerweise ist das der eigentliche Befehl, ohne den langen Environment-Block am Anfang.
Besonderheiten
Output geht per Mail — sonst weg
Was ein at-Job auf stdout oder stderr ausgibt, schickt atd per lokaler Mail an den User, der den Job angelegt hat — exakt wie bei cron. Ohne installiertes MTA (Postfix, sendmail, msmtp) wird die Mail still verworfen, der Output ist weg. Zwei sinnvolle Auswege: entweder einen MTA konfigurieren und mit mail die lokale Mailbox lesen, oder im Job selbst in eine Logdatei umleiten — echo '/home/me/job.sh >>/var/log/me.log 2>&1' | at 18:00.
atd muss laufen — sonst bleibt alles liegen
Auf Debian/Ubuntu ist der Daemon nach Installation oft nicht aktiv, auf vielen Server-Images ebenfalls nicht. at registriert in der Queue trotzdem brav — ohne atd bleiben die Jobs aber für immer dort liegen. systemctl status atd ist die schnelle Sanity-Prüfung; systemctl enable --now atd aktiviert ihn dauerhaft. Nach Reboot startet der Daemon automatisch und arbeitet noch ausstehende Jobs ab, deren geplante Zeit bereits in der Vergangenheit liegt — typischerweise sofort beim Start.
at.allow und at.deny regeln den Zugang
Analog zu cron.allow/cron.deny steuern /etc/at.allow und /etc/at.deny, wer überhaupt at-Jobs anlegen darf. Existiert at.allow, dürfen nur dort gelistete User at benutzen — alle anderen werden abgewiesen. Existiert nur at.deny, dürfen alle außer den gelisteten. Existiert weder noch, darf nur root. Die Default-Konfiguration unterscheidet sich je nach Distribution; auf Ubuntu ist meist eine leere at.deny aktiv, was effektiv jedem User Zugang gibt.
Job-IDs sind nicht stabil über Reboots
Die fortlaufenden Job-IDs aus atq sind nur innerhalb einer Daemon-Session eindeutig — nach Reboot oder atd-Restart kann der Zähler zurückspringen. Wer Jobs aus Skripten heraus anlegt und später per ID referenziert, sollte die ID direkt aus der at-Ausgabe parsen (2>&1 | grep '^job' | awk '{print $2}') und nicht auf Stabilität vertrauen. Für persistente Identifikation ist eine eigene Marker-Datei oder ein Logeintrag verlässlicher.
Default-Shell ist /bin/sh — nicht deine Login-Shell
Egal ob du Bash, Zsh oder Fish verwendest: at-Jobs laufen in /bin/sh. Auf Debian-basierten Systemen ist das ein symlink auf dash, einer minimalistischen POSIX-Shell ohne Bash-Features wie Arrays, [[ ]] oder {1..10}-Brace-Expansion. Wer Bash explizit will, ruft sie aus dem Job auf — bash -c 'eigene logik' oder ein Skript mit #!/usr/bin/env bash als Shebang und Übergabe per at -f.
Environment wird beim Anlegen eingefroren
at snapshotted dein komplettes Environment in dem Moment, in dem du den Job registrierst — alle Variablen, PATH, HOME, DISPLAY, DBUS_SESSION_BUS_ADDRESS werden 1:1 ins Job-Skript geschrieben. Das ist der Grund, warum at -c JOBID so lang ist. Praktische Konsequenz: Variablen, die sich nach dem Anlegen ändern, sieht der Job nicht mehr; Werte, die zum Anlege-Zeitpunkt galten, sind aber zuverlässig vorhanden. Für Desktop-Notifications, Tokens oder gerade exportierte Pfade ist genau das oft gewünscht.
batch läuft nur bei niedriger Last
Neben at gibt es batch — gleiche Syntax, aber der Job startet erst, wenn die System-Load unter 1.5 fällt (Default; per atd -l änderbar). Praktisch für CPU-intensive Jobs, die nicht zeitkritisch sind: man hängt sie in die batch-Queue, und der Daemon wartet eine ruhige Minute ab. echo 'cpu-intensive-task' | batch reicht — keine Zeitangabe nötig. In atq erscheinen batch-Jobs mit Queue-Buchstabe b statt a.
Für komplexere Wiederholung — systemd-Timer oder cron
at deckt einmalige Jobs ab, mehr nicht. Wer Wiederholung, Abhängigkeiten zwischen Jobs, persistentes Catch-Up nach Downtime, ressourcen-limitierte Ausführung oder strukturiertes Logging braucht, nimmt systemd-Timer: OnCalendar=, Persistent=true, gekoppelt an einen .service mit eigenem Logging via journald. Cron ist die klassische Alternative für reine Zeit-Trigger. at bleibt das beste Werkzeug, wenn die Aufgabe lautet: „einmal, dann nie wieder, möglichst ohne Setup-Aufwand”.
Weiterführende Ressourcen
Externe Quellen
- man 1 at (man7.org) — POSIX-Manpage zu
at,atq,atrmundbatch - man 8 atd (man7.org) — Manpage zum Daemon mit Optionen für Last-Schwelle und Polling-Intervall
- Arch Wiki: at — Installation, Aktivierung des Daemons, Beispiele
- Debian Wiki: at — Debian-spezifische Hinweise, Default-Konfiguration und Mail-Setup
- GNU mailutils — Lokale Mailbox lesen, in der at-Output landet, wenn kein externes Mail-System konfiguriert ist
Verwandte Artikel
- cron und Crontabs — Wiederkehrende Jobs als Gegenstück zu
at - systemd-Timer — Moderne Alternative mit Logging, Catch-Up und Service-Integration
- Prozess-Modell — Wie
atdJobs als Kindprozesse startet - Shell-Scripting Grundlagen — Skripte schreiben, die sich für
at -feignen - Signale — Bereits laufende at-Jobs sauber beenden