Service Accounts for Scheduled Transfer Jobs
Every scheduled job runs as somebody. That sentence sounds obvious until you audit a real machine and find what "somebody" turned out to mean: the nightly partner push runs as an administrator who left the company two years back, the invoice pull runs as whoever built it, and one job runs as the domain admin because "that was the only way it worked." None of these were decisions. They were defaults, and each one is a scheduled outage or a security finding waiting for its moment.
The fix is a service account — an identity created for the job rather than for a person — and this article covers the scheduling mechanics of getting one right: the specific rights a scheduled task needs, the logons it should be denied, the setup on both Windows and cron systems, what least privilege means on the far end of a transfer, and the request checklist that turns "who runs this job?" into a question with a written answer. It is part of our Scheduled Jobs series, and it builds directly on service account hygiene — read that for the naming, inventory, and lifecycle program; read this for what the scheduler itself demands from the identity.
Why the Job Needs Its Own Identity
Running a scheduled job as a person's account fails in four predictable ways, and it is worth being able to recite them, because you will need the list to win the "why can't it just run as me?" conversation.
- Password changes break the schedule. On Windows, a task set to run with nobody logged on stores the account's password. Rotate yours — as policy says you must — and every task holding the old copy starts failing logon. The job's reliability is now coupled to your password calendar.
- Departures break it worse. When the account's owner leaves, the account gets disabled — as it should — and every job riding on it dies the same hour. This is one of the most common causes of the mysterious mid-month transfer outage: offboarding worked exactly as designed.
- Security upgrades break it too. Multi-factor prompts, conditional access rules, forced interactive re-authentication — controls designed for humans arrive on human accounts, and an unattended job cannot answer any of them.
- The audit trail turns to mud. When a person's account moves ten thousand files a night, nobody can say which actions were the human and which were the machine. Attribution — this job, this account, these transfers — is what makes server logs reviewable at all.
Running it as an administrator account adds a fifth failure: blast radius. A scheduled job's credential is, by definition, stored somewhere; if that somewhere leaks, the attacker holds whatever the account holds. An identity that can touch one folder and one remote host is a contained incident. An admin identity is a network compromise. The arithmetic of that difference is spelled out in the blast radius of one account.
Scoping: one account per what?
The two extremes both fail. One account per individual task multiplies secrets and review work until people stop doing either well. One shared "svc-automation" account across every machine and flow recreates the blast-radius problem and makes attribution meaningless again. The workable middle: one account per flow, or per small family of related jobs on one machine. The invoice push and its companion cleanup job can share svc-invoicepush; the unrelated payroll pull on the same machine gets its own. A decent test: if two jobs would be retired together and touch the same data, they can share an identity. If not, split them.
The Windows Setup, Step by Step
Assume the nightly invoice push from our Task Scheduler walkthrough: a script under C:\Jobs\InvoicePush, running as svc-invoicepush. Use a domain account if the job must reach domain resources like file shares; a local account confines the identity to one machine, which is a feature when the job's world is local.
:: create the account (prompts for a long, random password) net user svc-invoicepush * /add /comment:"Runs invoice-push-nightly. Owner: finance-it team" :: job folder: the account gets modify on its own tree, nothing else icacls C:\Jobs\InvoicePush /grant "svc-invoicepush:(OI)(CI)M"
Then the rights, which is where scheduled tasks differ from human logins. In Local Security Policy (or the equivalent group policy), under User Rights Assignment:
- Grant "Log on as a batch job." This is the logon type Task Scheduler uses to run a task with nobody at the keyboard. Without it, the task's logon fails before your script ever starts — an error that reads like a wrong password and wastes an afternoon. Administrators typically have it already, which is exactly why nobody notices the requirement until the first least-privilege account arrives.
- Add the account to "Deny log on locally" and "Deny log on through Remote Desktop Services." The account exists to run batch jobs; it should be incapable of becoming somebody's desktop session. If its password ever leaks, these denials convert "attacker logs in" into "attacker cannot use the front doors."
- No administrator groups. A transfer job that owns its folders and speaks to a remote server needs none, and the highest-privileges checkbox in the task should stay clear to match.
One honest policy tension to settle deliberately: password expiry. Let the domain's expiry policy apply and the account's password will one day expire unattended — and the stored task credential fails exactly like the personal-account case you were escaping. The common, defensible choice for service accounts is a long random password that does not auto-expire, rotated deliberately on a calendar with the task updated in the same change window. How to store and rotate it without breaking the schedule is the subject of where scheduled jobs keep their credentials. If your directory team offers managed service accounts — directory-managed identities whose passwords rotate automatically and are never known to any human — they remove this tension entirely for scheduled tasks; registering a task under one is a command-line affair rather than a console checkbox, and worth the detour where available.
The Cron Side: A Job User Done Right
The Unix version is shorter because there is no stored password to manage — cron runs the job as the crontab's owner, and the owner should be a dedicated user:
# dedicated user: no login shell, locked password, private home useradd -m -s /usr/sbin/nologin svc-transfer passwd -l svc-transfer chmod 700 /home/svc-transfer # the job's crontab belongs to that user (edit it as root) crontab -u svc-transfer -e
Two details here surprise people, both worth knowing precisely. A nologin shell does not stop cron: cron does not use the account's login shell to run jobs (it uses its own, /bin/sh by default), so the account can be un-loginable and still run its schedule — which is exactly the combination you want. And a locked password (passwd -l) blocks password authentication without disabling the account, so cron jobs and key-based operation continue while the password door stays welded shut. The user's home directory then becomes the natural, permission-protected place for the job's SSH keys and configuration.
Remember: on both platforms the goal is the same shape — an identity that can run scheduled work and cannot do anything else. Batch logon yes, desktop logon no; folder rights yes, admin rights no; schedule yes, shell no.
Moving Existing Jobs onto the Right Identity
Most readers are not creating jobs fresh — they are inheriting an estate where "somebody" already means the wrong accounts. Start by finding out who runs what today:
:: Windows: every task with its run-as account, into a spreadsheet
schtasks /query /v /fo csv > tasks.csv
:: scan the "Run As User" column for people's names and admin accounts
# Unix: which users have crontabs at all
for u in $(cut -f1 -d: /etc/passwd); do
crontab -l -u "$u" >/dev/null 2>&1 && echo "$u has a crontab"
done
# /etc/crontab and /etc/cron.d lines name their user in the sixth field
Every task running as a person or an administrator goes on the migration list, worst blast radius first. Then move one flow at a time, inside a change window: create the account, grant its folder rights, place its credentials as that account, repoint the task (or move the crontab lines), and run the job once by hand — schtasks /run on Windows, sudo -u svc-transfer /opt/jobs/.../push-invoices.sh on the Unix side — watching the job's log and the remote server's before trusting the next scheduled run.
Two first-run details catch people. On Windows, a brand-new account has no user profile until something logs it on; let that happen during your supervised test run, not implicitly at two in the morning. And on either platform, the first SSH-based connection to a host prompts to accept the server's host key — a question an unattended job cannot answer, so it simply hangs. Make that first connection interactively as the service account so the key is verified and recorded, as covered in host keys and known_hosts.
Least Privilege Reaches the Far End Too
A transfer job has two identities: the local account the scheduler runs it as, and the remote account it authenticates with when it connects. Scoping the first and not the second is half a job.
On the remote server, the job's account should see only its own corner: an upload flow gets write access to one drop directory and nothing to read elsewhere; a pull flow gets read on one outbox; either way the account is confined to its own tree rather than roaming the server's filesystem. Whether your flow pushes or pulls changes which side holds which rights — push vs pull walks that decision — but the principle is symmetric: the remote account is part of the job's blast radius, so trim it with the same enthusiasm you brought to the local one. When the remote end belongs to a partner, ask for the narrow account explicitly; partners default to handing out whatever their standard template grants, and nobody ever comes back later to shrink it.
If the far end is your own Windows server, this is straightforward to do well. In Sysax Multi Server, each automated flow can get its own user account with its own folder scope, and the server's activity logging — to file or to a database — then attributes every login and transfer to that specific job identity. That attribution is what makes the periodic review meaningful: the log answers "what did svc-invoicepush actually do last month?" with evidence instead of memory.
Keys Beat Passwords for Job Identities
Where the protocol allows it — SFTP above all — authenticate scheduled jobs with SSH keys rather than passwords. A key pair splits the secret: the server holds the public half, which is harmless to expose, and the job holds the private half, a file whose permissions you control. There is no password to store in a scheduler, no expiry policy to collide with, and rotation is a deliberate act rather than a domain-wide surprise. Generation and safe storage are covered in generating and storing SSH keys; the short version for jobs is that the private key lives in the service account's protected home or profile, readable by that account alone.
Two job-specific cautions. First, generate the key for the service account, not for yourself. A key made on an administrator's workstation and copied around is a personal credential wearing a costume — when that person leaves, nobody is sure what still authenticates with it, which is precisely the mess retiring keys at offboarding exists to clean up. Second, an unattended job cannot type a passphrase, which forces an honest trade between passphrase-free keys and filesystem protection — weighed properly in the credentials article of this series.
Lockouts: When the Job Attacks Itself
Here is a failure mode unique to automated identities. A credential goes stale — password rotated, key retired — and the job does not know it. It connects, fails, and retries. And retries. A scheduled task with a repeating trigger, or a retry loop inside the script, can present dozens of failed logins an hour, and now two bad things happen at once. If the account has a lockout threshold, the job locks its own account, converting a stale credential into a hard outage — including for any other job sharing the identity, which is one more argument for narrow scoping. And in the server's logs, the pattern is indistinguishable at a glance from a password-guessing attack, so it can trigger the defenses described in our guide to monitoring authentication attacks.
Treat repeated authentication failure from a service account as a page-worthy signal in its own right: it is either an attacker using your job's name or a broken job burning its retry budget, and both deserve a human promptly. The operational playbook for lockout storms — finding the source, unlocking safely, fixing the credential — is in authentication failures and lockouts. The prevention is procedural: credential changes for job identities happen in a change window, with the job's next run watched, never on a Friday afternoon by someone who does not know the schedule exists.
The Request Checklist: Making It a Written Answer
Every service account should be born with paperwork — not bureaucracy for its own sake, but the answers the next administrator, the auditor, and the incident responder will otherwise have to guess. Put this template in your ticket system and require it filled before the account is created:
SERVICE ACCOUNT REQUEST -- scheduled transfer job Account name: svc-invoicepush (per naming standard) Purpose: pushes daily invoice files to PartnerCo SFTP Jobs it runs: task \Transfers\invoice-push-nightly on APP01 Reaches: sftp.partnerco.example (push, upload-only) Local rights: modify on C:\Jobs\InvoicePush; log on as batch job Denied: local logon, remote desktop, all admin groups Remote auth: SSH key held in account profile (no password auth) Owner: finance-it team (contact: shared mailbox) Rotation plan: key rotated yearly, in a change window, job watched Review date: next scheduled access review Decommission note: retire with the invoice flow; remove key at partner
Ten minutes to fill in; hours saved every time anyone afterward asks what this account is for. Note the owner line: a team, not an individual. Accounts owned by a person inherit that person's departure date.
Ownership, Reviews, and Where to Go Next
The checklist's last three lines are the ones that keep the account healthy over the years. Ownership means someone answers when the job breaks or the account turns up in a finding. The review date feeds the periodic sweep — walking the account list against the jobs that actually exist, catching identities whose jobs died long ago and rights that quietly grew — a discipline that slots into the broader program described in periodic access reviews for transfer systems. And the register of jobs that makes such reviews possible is part of scheduled job hygiene, later in this series.
One last scoping observation. The wiring this article describes — batch rights, denied logons, stored passwords, watched rotations — exists because a general-purpose scheduler knows nothing about what it launches. When the scheduled thing is purely a transfer, you can shrink the surface instead: a transfer task built in Sysax FTP Automation runs on its own schedule with retry and email notification, no Task Scheduler task and no batch-logon setup required, leaving the remote credentials in the product's connection profile as the main secret to govern. The service-account principles still apply to what remains; there is simply less of it.
From here, the natural next reads are where scheduled jobs keep their credentials for the secret-storage half of this story, and service account hygiene for the org-wide lifecycle program this article's mechanics plug into.
Frequently Asked Questions
Why shouldn't scheduled jobs just run as my admin account?
What is the "Log on as a batch job" right and why does my task need it?
Should service account passwords expire?
Can a Linux account with a nologin shell still run cron jobs?
How many jobs should share one service account?
From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.
