LearnRHCSA (EX200)Deploy, Configure, Maintain

at: One-Shot Jobs

at - a hands-on Linux lab on a real virtual machine.

Run a command once, at a time you name, then have it clean itself up. Queue a one-shot job with echo cmd at <time, where <time is a clock time (at 14:00), a relative offset (at now + 5 minutes), or a nickname (at teatime). List pending jobs with atq (job number, run time, queue, owner), read a job's stored script with at -c, and cancel one before it runs with atrm. None of it fires unless the atd daemon is running: systemctl enable --now atd. Use at for run-it-once-later work; use cron for recurring schedules. Serves EX200 deploy-configure: schedule tasks using at and cron.

A migration finishes at 22:00. The moment it lands, one script has to run: flush a cache, send a notice, flip a flag. Exactly once. You do not want it in the crontab, because a crontab entry runs forever, every day, and you would have to remember to go back and delete it tomorrow. You just want this one command to fire later tonight and then be gone.

You could set an alarm and stay up. Or you could hand the job to the machine and go home. That is what at is for: it runs a command once, at a time you name, and then forgets it. This lesson takes a one-shot job from queued, to listed, to inspected, to cancelled, and shows the one service that has to be alive for any of it to work.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The outputs are from a real RHEL 10 machine (AlmaLinux 10.2). Two values here depend on the machine and the clock: the job number at assigns and the run time it prints back. Yours will read a different number and a different date. What is stable everywhere is the SHAPE: at prints job N at <full date>, and atq lists one row per pending job.

at schedules a command to run once, at a single future moment, then discards it. Think of it as a sticky note stuck to a clock: at the time written on it, the note is read, the command runs, and the note is thrown away. It never runs again.

That one-shot nature is the whole point, and it is what sets at apart from cron. A crontab entry is recurring: it fires on a schedule, over and over, until you remove it. An at job fires exactly once and cleans itself up. When a task says run this tonight, use at. When a task says run this every night, use cron.

Real work is full of do-it-once-later jobs. Kick off a backup after the business day ends. Restart a service during the maintenance window at 3 a.m. Send a reminder in twenty minutes. Delete a temp directory an hour from now. None of these belong in a crontab, because none of them should ever repeat, and a stale recurring entry is its own kind of bug.

at is the clean tool for that shape of work. You name the time in plain terms, you hand it the command, and it takes over. No entry to clean up afterward, because the job removes itself the moment it runs.

You do not type the command as an argument to at. Instead, at reads the command to run from its standard input, and it takes the TIME as its argument. The clean way to feed it one command is to echo the command and pipe it in: echo '<command>' | at <time>.

Here you queue a job that writes a line to a file, set to run one hour from now with at now + 1 hour. When it accepts the job, at prints back the job number it assigned and the exact time the job will run. Queue the one-shot job:

echo 'echo hi > /tmp/atout' | at now + 1 hour

prompt: [root@servera ~]# answer: echo 'echo hi > /tmp/atout' | at now + 1 hour output: job 2 at Sun Jul 5 22:16:00 2026 hint: echo the command, pipe it into at, and give at the time: echo '...' | at now + 1 hour

at read the command echo hi > /tmp/atout from the pipe, and took now + 1 hour as the time to run it. Its reply, job 2 at Sun Jul 5 22:16:00 2026, confirms two things. It assigned this job the number 2. And it will run at 22:16:00 on that date, exactly one hour after the moment the command was accepted. The job number is the handle you use later to list, inspect, or cancel this job. Nothing has run yet; the job is now sitting in the queue, waiting for its time.

The job number and the date and time in your reply will not match the ones shown. at hands out numbers in sequence and computes the run time from your machine's current clock, so on your terminal you might see job 5 at a different date entirely. Read the SHAPE, not the digits: job <N> at <full date and time> is the confirmation that the job was accepted.

The flexible part of at is how you say WHEN. It understands both clock times and relative offsets, in plain terms. A few forms cover almost everything the exam asks:

All of these go in the same place: the argument after at. The command to run still arrives on standard input through the pipe. So the whole pattern is always echo '<command>' | at <one of these time forms>.

When you are just experimenting and want the job to fire soon, at now + 1 minute is the friendliest form. It is unambiguous, it runs almost immediately, and it lets you watch the whole lifecycle without waiting. teatime is charming but easy to mistype under exam pressure; a plain now + N minutes never surprises you.

Once you have queued a job or two, you need to see what is pending. That is atq, the at-queue lister. Run it with no arguments and it prints one row per job that has not run yet. Each row has four fields: the job number, the run time, a queue letter, and the owner.

List the pending jobs:

atq

prompt: [root@servera ~]# answer: atq output: 1 Sun Jul 5 22:16:00 2026 a root 2 Sun Jul 5 22:16:00 2026 a root hint: The queue lister is atq, run on its own with no arguments: atq

Two jobs are pending, one per row. Read the columns left to right: the first field is the job number (1, then 2), the same handle at printed when you queued each one. Next is the run time, the full date and time the job will fire. Then a single letter, here a, which is the queue the job sits in (the default at-queue is named a). Last is the owner, root, the user who scheduled it. The job number in the first column is what you feed to the next two commands to inspect or cancel a specific job.

Your atq will list whatever jobs YOUR machine has pending, with their own numbers, times, and owner, so it will not match these two rows. An empty queue prints nothing at all, which is the normal, correct answer when no jobs are waiting. As with findmnt on an unmounted path, no output from atq is itself information: it means the queue is clear.

Sometimes you need to know exactly what a queued job will do before its time comes. at -c <job-number> prints the full job as at stored it: not just your one command, but the entire environment at captured when you scheduled it, followed by your command at the very bottom.

That environment block at the top looks noisy, but it is why at jobs behave the same at 3 a.m. as they did when you queued them: at records your shell settings so the job runs in the same world. Inspect job number 2 with:

at -c 2

The output is long: dozens of export lines that recreate your environment, and then, on the last line, the command you scheduled. You are not meant to read every line. You scroll to the bottom, confirm the command is the one you meant, and move on. at -c is the answer to what will this job actually run, without waiting for it to run.

None of this works if the atd service is not running. atd is the daemon that watches the clock and runs jobs when their time comes. On a fresh RHEL 10 machine it may be installed but not started, so at will accept and queue your job, but the job will never fire, because nothing is watching the clock. Turn the daemon on, and set it to survive a reboot, with systemctl enable --now atd. If a queued job never runs, check systemctl is-active atd first; that is the classic reason.

A queued job is not a commitment. If you scheduled something you no longer want, you delete it before its time with atrm <job-number>. You get the job number from atq. Once removed, the job is gone from the queue and will never run.

Cancel the job numbered 2:

atrm 2

prompt: [root@servera ~]# answer: atrm 2 output: hint: The remove command is atrm, then the job number you saw in atq: atrm 2

No output, which is success: atrm is silent when it removes a job. Job 2 is now gone from the queue, and running atq again would show it no longer listed. This is the whole cleanup story for at: a job either runs at its time and removes itself, or you remove it yourself with atrm beforehand. There is no stale recurring entry left behind either way, which is exactly why at is the right tool for run-it-once work.

Scaffolding off. No command is printed this time. You have every piece you need.

You queued a job a moment ago and want to confirm it is really pending before you trust it. You do not want to inspect one job's script, and you are not cancelling anything yet. You just want the plain list of everything waiting to run, one row per job, showing each job's number so you can act on it. Which single command, run on its own, prints that list?

prompt: [root@servera ~]# answer: atq output: 1 Sun Jul 5 22:16:00 2026 a root 2 Sun Jul 5 22:16:00 2026 a root hint: It is the at-queue lister, run bare with no arguments. Three letters: atq.

atq prints the pending queue, one row per job: job number, run time, queue letter, owner. It is how you find the job number you then hand to at -c to inspect or atrm to cancel. Remember the trio as a set: at to schedule, atq to list, atrm to remove. When a task says confirm the job is queued, atq is the confirm.

You earned this cheat sheet. Every row is a form you just ran or built:

The pipe pattern is always echo '<command>' | at <time>. These rows list the at <time> forms and the queue tools:

The one thing to burn in for the exam: at runs a job ONCE and then it is gone, and none of it fires unless atd is running. When a task says do this once, later, reach for at. When it says do this on a schedule, reach for cron.

The single most common reason an at job silently never runs is that atd was never started. Make systemctl enable --now atd the first thing you do on a fresh machine before you trust any scheduled job. enable makes it survive a reboot, --now starts it this instant, and one command does both.

The practice terminal has walked you through the whole loop. You queued a one-shot job with echo 'cmd' | at <time>. You met the many time forms, from 14:00 to now + 5 minutes to teatime. You listed the queue with atq, read a job's stored script with at -c, and cancelled one with atrm, all resting on atd being alive. Every one of those you typed yourself.

The deploy-configure module mission, Operation Rollout, is where you run these against a real RHEL 10 machine. A full VM boots for you, with a live atd and a real clock. The mission hands you deploy-and-configure objectives that use exactly what you practiced here. Schedule a job to run at a named time, confirm it landed in the queue, and cancel one that should not run. One difference from this lesson: the mission shows no commands. You read the objective, recall the at and atq and atrm forms, and type them. That recall is what makes it stick on exam day.

Finish the other deploy-configure lessons, then go schedule a real one-shot job.

Practice at: One-Shot Jobs in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.