Updated
Contractor or ex-employee left? How to revoke their server access
The contract has ended or an employee has left, and the server your business runs on is one they set up. You don't know whether they can still log in, or which accounts, keys and scheduled jobs they left behind. This guide is for owners with a VPS and no technical background, and it covers one job: taking back access, in an order that doesn't lock you out of your own server. It doesn't cover working out what runs on a server you've taken over; that is in Inherited a server: what to check first. It also doesn't cover investigating a break-in; that is in Was my Linux server hacked?.
How to use this guide:
Every command is labeled. Read-only commands only look. CHANGE commands modify the server; each comes with a backup step and an Undo line. Run changes one at a time.
Most commands start with
sudo, which runs them with admin rights and may ask for your password.Replace
THEIR_USERwith the account name they used (step 2 shows it),YOUR_USERwith yours andSERVER_IPwith the server's address.Keep one logged-in terminal window open from start to finish. It is your way back if a change goes wrong.
If anything looks wrong at any point, stop and go to the section "Stop here if anything looks wrong" near the end.
1. Before you touch the server: list everything they could reach
The server is only one of the doors. Write down each place they had access to and how: their own login, a shared password they knew, or a key or token made for them. Record where things are, never the passwords themselves.
| Where | What to look for | What to do there |
|---|---|---|
| Cloud account (the provider's website, billing, API access keys) | Their own user, a shared login they knew, access keys created for them | Remove their user, change the shared password, delete their access keys, turn on two-factor login |
| Domain registrar and DNS | Same as above | Same as above |
| Hosting control panel or web terminal, if you use one | A panel user or shared panel password | Remove their user or change the password |
| The server itself (SSH) | Accounts, keys, admin rights | Steps 2 to 4 below |
| Code repository and deploy pipeline | Their membership, deploy keys, tokens, pipeline secrets | Remove them; replace keys and tokens (step 5) |
| Database | Their own database account, the app's password they saw | Step 5 |
| Other services (email sending, payments, SMS, file storage, backups, monitoring) | Their user, API keys they handled | Remove their user; replace the keys (step 5) |
| Password manager, shared documents, team chat | Passwords posted or shared with them | Remove them; treat anything they saw as known |
Do the cloud account first. Someone who can still sign in there can reset the server's root password, open its web console or copy its disk, no matter what you change on the server itself.
If you parted on good terms, ask them for a list of the accounts, keys and scheduled jobs they set up. Use it as a starting point and still check everything below.
2. Find who can still log in to the server
Read-only. Accounts and admin rights:
getent passwd | awk -F: '$7 !~ /(nologin|false|sync|shutdown|halt)$/ {print $1, $3, $6, $7}'
getent group sudo wheel admin
sudo grep -rE '^[^#]*ALL' /etc/sudoers /etc/sudoers.d/
The first line lists accounts that can open a command line: name, account number, home folder, shell.
rootand your own account are expected; every other name needs an owner you can point to.The second line lists members of the admin groups (
sudoon Debian and Ubuntu,wheelon CentOS, Rocky, AlmaLinux and other Red Hat-family systems). A group that doesn't exist on your system is simply not shown.The third line lists every rule that grants admin rights, with the file it comes from. A file in
/etc/sudoers.d/named after them, or a line with their account name, gives them admin rights.%sudomeans everyone in thesudogroup, andNOPASSWDmeans without typing a password.
Read-only. Passwords and SSH settings:
sudo passwd -S THEIR_USER
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|authorizedkeysfile) '
passwd -Sshows whether the account has a password:PorPSmeans a password is set,LorLKmeans locked,NPmeans no password. Run it for each account from the first list.sshd -Tprints the SSH settings actually in use.passwordauthentication yesmeans anyone who knows an account's password can log in from anywhere.permitrootloginsays whether root can log in directly.authorizedkeysfilesays where key files are kept; the default is.ssh/authorized_keysin each home folder (some systems also list.ssh/authorized_keys2, which the next command covers too), and if yours says something else, look there too.
Read-only. SSH keys. A key works like a copy of a door key: whoever holds the matching private key can log in to that account without a password.
sudo find /root /home /srv /opt /var -path '*/.ssh/authorized_keys*' -type f -printf '\n%p (last changed %TY-%Tm-%Td)\n' -exec ssh-keygen -lf {} \; 2>/dev/null
This one line finds every key file in the usual places and prints, for each file, its location, the date it last changed, and one line per key: a fingerprint and a label such as
mike@macbook. If the first command in this step showed a home folder somewhere else, add that folder to the list afterfind.The label is free text that anyone can type, so it is a hint, not proof.
no commentmeans the key has no label.A key that nobody on your side can name should be treated as theirs.
Read-only. Recent logins:
who
last -a -n 20 THEIR_USER
lslogins THEIR_USER
whoshows who is logged in right now.lastshows their most recent logins and where they came from. Some newer systems no longer include it.lsloginsshows a summary of the account, including the last login when the system keeps that record, and how many programs are running as that account.
A login after the day they left, or a session right now that you can't explain, is a reason to stop and go to "Stop here if anything looks wrong" below. For the full login history from the logs, use step 1 of Was my Linux server hacked?.
What this does not tell you: whether they can get in some other way (the cloud console, a panel, a VPN), or whether they kept a copy of passwords or data. It lists the doors on this server only.
3. Add your own access first, and prove it works
Do this before you remove anything of theirs. If your only way in is a shared account or a key they set up, removing it can lock you out.
Keep your current session open in one window for the whole process.
Make your own key on your own computer, if you don't have one yet. This runs on your computer and changes nothing on the server:
ssh-keygen -t ed25519 -C "yourname-laptop"It creates two files: a private key that never leaves your computer and must not be shared, and a public key ending in
.pub, which is the part the server gets.CHANGE: add your public key to your account on the server. From your own computer:
ssh-copy-id -i ~/.ssh/id_ed25519.pub YOUR_USER@SERVER_IPThis appends your public key to
YOUR_USER'sauthorized_keysfile on the server. Ifssh-copy-idisn't available (for example on Windows), paste the contents of the.pubfile as a new line in that file instead. Undo: delete the line ending inyourname-laptopfrom that file (step 4 shows how to delete one line).Recommended if you've been using a shared account (root, or an account they created): CHANGE: create your own admin account, then repeat item 3 for it.
sudo adduser YOUR_NEW_USER sudo usermod -aG sudo YOUR_NEW_USERaddusercreates the account and asks you to set its password (Debian and Ubuntu; on Red Hat-family systems usesudo useradd -m YOUR_NEW_USERand thensudo passwd YOUR_NEW_USER).usermod -aG sudoadds it to the admin group (usewheelinstead ofsudoon Red Hat-family systems). Undo:sudo userdel -r YOUR_NEW_USERremoves the account and its home folder.Test from a new window, leaving the old one open:
ssh YOUR_USER@SERVER_IP sudo whoamiThe first line logs you in with your own key. The second should print
root, which proves you still have admin rights. If either fails, don't go on; use the session you kept open to find out why.Check your emergency way in. Sign in to your cloud provider's website and find the server's web console (often called VNC or serial console). It works even when SSH doesn't.
4. Close their ways in: back up first, then lock
Lock first, delete later. Locking can be undone in one command; deleting can't.
Read-only. What runs as them, and room for a backup:
ps -u THEIR_USER -o pid,lstart,cmd
sudo crontab -l -u THEIR_USER
sudo du -sh /home/THEIR_USER
df -h /root
pslists programs running as their account right now, with start times.crontab -llists their scheduled jobs;no crontab for …means there are none.dushows how big their home folder is, anddfshows whether there is room to keep a copy of it.
If your website, app or backups run under their account, lock their logins as below but don't delete the account yet. Plan to move that work to a company-owned account first (step 6), with help if needed.
Makes backup copies; changes nothing else.
sudo mkdir -p /root/offboarding-backup
sudo cp -a /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers /etc/sudoers.d /root/offboarding-backup/
sudo tar -czf /root/offboarding-backup/home-THEIR_USER.tar.gz /home/THEIR_USER
mkdircreates a backup folder that only root can read.cp -acopies the account, password, group and admin-rights files exactly as they are now.tarpacks their home folder into one archive file.
Keep this folder until you're sure nothing broke. It contains password hashes, so remove it once you no longer need it.
CHANGE: lock the account.
sudo usermod -L -e 1 THEIR_USER
-L locks the password (the same as sudo passwd -l THEIR_USER), and -e 1 marks the account as expired, which on standard setups also blocks logins with SSH keys. Locking the password on its own does not stop key logins.
Check it (read-only):
sudo passwd -S THEIR_USERshould showLorLK, andsudo chage -l THEIR_USER | grep 'Account expires'should show a date in 1970.An expired account may also stop that account's scheduled jobs from running. If
crontab -labove showed jobs you depend on, handle them in step 6 first.Locking doesn't end a session that is already open.
Undo:
sudo usermod -U -e '' THEIR_USERunlocks the password and removes the expiry.
CHANGE: remove their keys. For their own account, move the whole key file into the backup folder:
sudo mv /home/THEIR_USER/.ssh/authorized_keys /root/offboarding-backup/authorized_keys.THEIR_USER
Undo: sudo mv /root/offboarding-backup/authorized_keys.THEIR_USER /home/THEIR_USER/.ssh/authorized_keys
For a shared account (root, deploy and so on) where their key sits next to yours, remove only their line. This example uses root; for another account, use /home/SHARED_USER/.ssh/authorized_keys and name the backup copy after it.
sudo cp -a /root/.ssh/authorized_keys /root/offboarding-backup/authorized_keys.root
sudo awk 'NF && !/^#/ {print NR": "$NF}' /root/.ssh/authorized_keys
sudo sed -i '4d' /root/.ssh/authorized_keys
cp -akeeps a copy of the file as it is now.awkprints each key's line number and the label at the end of the line. A long string of random characters instead of a label means that key has no label.sed -i '4d'deletes line 4 of the file. Replace4with the line number of their key. Delete one line at a time and runawkagain before the next one, because the numbers shift.
Then run the key command from step 2 again: your key should still be listed and theirs should be gone. Log in from a new window once more to be sure. Undo: sudo cp -a /root/offboarding-backup/authorized_keys.root /root/.ssh/authorized_keys
CHANGE: remove their admin-rights file, if step 2 showed a file in /etc/sudoers.d/ just for them:
sudo mv /etc/sudoers.d/THEIR_FILE /root/offboarding-backup/
sudo visudo -c
mv moves the file out so it no longer applies, and visudo -c checks that the remaining admin-rights setup is still valid. Never edit /etc/sudoers with a normal editor; a mistake there can take away everyone's admin rights. If their rule is a line in /etc/sudoers itself, leave it for now (a locked account can't log in to use it) and remove it with sudo visudo when you delete the account. Undo: sudo mv /root/offboarding-backup/THEIR_FILE /etc/sudoers.d/
CHANGE: change the passwords of shared accounts they knew.
sudo passwd root
sudo passwd SHARED_USER
Each line sets a new password for that account. It asks twice, and nothing appears on screen while you type. Save the new password in your password manager before you close anything, and if you log in with it, test it from a new window. Undo: there is no going back to the old password, and that's the point. The session you kept open lets you set it again if something goes wrong.
5. Change the passwords and keys they saw
Anything they could see, they could have copied. Changing a secret only on the server isn't enough, because your app has to use the new one too. For each secret, work in this order: create the new one, put it where it's used, restart the app, check that the site works, and only then turn off the old one. Until the old one is turned off, you can switch back.
Read-only. Find which secrets exist, showing names only:
sudo find /root /home /srv /opt /var \( -name '.env' -o -name '.env.*' \) -type f -not -path '*/node_modules/*' 2>/dev/null
sudo grep -oE '^(export +)?[A-Za-z_][A-Za-z0-9_]*=' /path/to/app/.env
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' YOUR_CONTAINER | cut -d= -f1
sudo find /root /home /srv /opt /var -path '*/.ssh/id_*' -not -name '*.pub' -type f 2>/dev/null
The first line lists
.envfiles, where apps often keep passwords and keys (file locations only). Files ending in.exampleare usually templates.The second prints only the setting names in one of those files, such as
DB_PASSWORD=, never the values. Replace the path with a real one from the first line.The third prints the names of the settings given to a Docker container, without values. Skip it if you don't use Docker.
The fourth lists private key files on the server (locations only). They let this server log in somewhere else, for example to pull code. If they had access to that account, make a new key and replace the old one wherever it's registered.
Names containing PASSWORD, SECRET, TOKEN, KEY or URL (connection addresses often include a password) are the ones to change. Don't open these files on a shared screen, and don't paste them into a chat or an AI tool.
Read-only, if your database allows it. List database accounts (no passwords are shown):
sudo mysql -e "SELECT user, host FROM mysql.user;"
sudo -u postgres psql -c '\du'
The first is for MySQL or MariaDB, the second for PostgreSQL; use the one you have. An account with their name should be removed, and a host of % means that account can connect from anywhere. Removing database accounts and changing their passwords are changes to make with the app's config at hand, ideally with help.
What to change, and what to update afterwards:
| Secret | Change it in | Then update |
|---|---|---|
| Database passwords | The database | The app's config (.env or similar), then restart the app |
| App secrets (session or signing secrets, internal API keys) | The app's config | Restart the app; a new session secret usually logs all users out |
| Cloud access keys | The cloud provider's website (users and access keys) | Everywhere the server uses them, such as .env or the cloud command-line tool's config |
| Deploy keys and tokens | Your code host and deploy pipeline settings | The server's code-pulling setup and pipeline secrets |
| Third-party API keys (email, payments, SMS) | Each provider's dashboard | The app's config, then restart |
| Your own passwords they saw (cloud, domain, panel, email) | Each website | Turn on two-factor login |
Some changes have side effects: users logged out, payment notifications failing until the new key is in place. Do one at a time, at a quiet hour, and get help for anything you're unsure about.
6. Look for jobs and services they left running
Read-only.
sudo crontab -l -u THEIR_USER
sudo grep -rn 'THEIR_USER' /etc/crontab /etc/cron.d /var/spool/cron 2>/dev/null
systemctl list-timers --all --no-pager
sudo grep -rl -E 'User=THEIR_USER|/home/THEIR_USER' /etc/systemd/system 2>/dev/null
ls /var/lib/systemd/linger/ 2>/dev/null
ls -la /home/THEIR_USER/.config/systemd/user/ 2>/dev/null
crontab -l -ulists their personal scheduled jobs.grep … cronfinds system-wide scheduled jobs that mention their account or home folder.list-timerslists the other kind of scheduled job on Linux (systemd timers); look for names you don't recognize.grep … systemdfinds services set up to run as their account or from their home folder.The
lingerfolder lists accounts allowed to keep their own services running after logging out. Their name there means something of theirs may keep running.The last line shows services they set up for their own account, if any.
How to read what you see:
A normal job (backup, certificate renewal, log cleanup) running as them or from their home folder: don't just delete it, because your site may depend on it. Move it to a company-owned account first, with help if needed, then turn theirs off.
A job that sends data to a server you don't recognize, downloads and runs something from the internet, or that nobody can explain: stop and go to "Stop here if anything looks wrong" below.
Deal with their scheduled jobs before you delete the account. Don't assume deleting the account cleans them up.
CHANGE: turn off a job or service you've confirmed you don't need.
sudo crontab -l -u THEIR_USER | sudo tee /root/offboarding-backup/crontab.THEIR_USER
sudo crontab -e -u THEIR_USER
sudo systemctl disable --now SERVICE_NAME
The first line saves a copy of their job list in the backup folder (and shows it on screen).
crontab -eopens their job list in an editor; put#at the start of a line to switch that job off.systemctl disable --nowstops a service and keeps it from starting again at boot.Undo: remove the
#, or restore the whole list withsudo crontab -u THEIR_USER /root/offboarding-backup/crontab.THEIR_USER. For a service:sudo systemctl enable --now SERVICE_NAME.
7. Later: delete the account
Wait until step 6 is done and a week or two has passed with nothing broken. Then:
CHANGE, with no simple undo.
sudo userdel THEIR_USER
sudo find / -xdev -nouser -ls 2>/dev/null | head -n 20
userdelremoves the account but leaves the home folder in place. It refuses while programs are still running as that account. Delete the folder separately once you're sure you have the archive from step 4 and don't need it.The
findline (read-only) lists files still owned by an account that no longer exists, so you can decide what to keep.Undo: none that is simple. You would have to recreate the account and restore its files from the archive. That's why this comes last.
When you're done, write down, somewhere off the server, the date, what you locked, removed and changed, and where the backups are.
Stop here if anything looks wrong
Stop cleaning up if you see any of these:
a login by them, or by an account you don't know, after the day they left, or a session right now that you can't explain;
keys added or changed after they left (the dates in step 2), or accounts and admin rights nobody expected;
scheduled jobs or services that download and run things, or send data somewhere you don't recognize;
programs you can't explain using a lot of CPU or network.
Locking an account doesn't destroy evidence, but deleting files, keys, jobs or accounts does. Save what you've seen somewhere off the server, then follow Was my Linux server hacked? from its step 0: take a snapshot, limit traffic with your cloud provider's firewall rules, and get professional help. Questions about what a former employee or contractor may legally have done are for a lawyer, not for this guide.
Doing this in OpsMate
OpsMate puts an SSH terminal and an AI assistant on the same server page. You can type every command in this guide yourself; most of the checks need sudo, so running them yourself is the straightforward path. You can also ask in plain language, for example "summarize which accounts logged in over SSH in the last 30 days, and from which IP addresses". The AI proposes troubleshooting commands, and beyond logs it can use ps, ss and journalctl for checks. After a command has run, click Analyze to get a conclusion; the commands and output stay in the terminal so you can check it against the raw lines (see AI and SSH commands in one workspace). When you click Analyze, the command output is sent to cloud AI for analysis; what the desktop app keeps on your own machine by default is your SSH credentials. Config and .env files hold the very secrets you are about to replace, so never send their contents: use the names-only commands above and share only redacted excerpts.
The changes in steps 3 to 7 are yours to type, one at a time, after the backup. If you connect with the key you made in step 3, the desktop app keeps your SSH credentials on your own machine by default.
Boundaries
OpsMate is not an access-management system or a bastion host. It has no feature for revoking access, locking accounts or replacing keys. The AI is mainly for troubleshooting: dangerous commands are blocked. Every change in this guide is one you type yourself, with a backup and a way back. For what OpsMate does and doesn't do on its own, see the FAQ.
A clean result is not proof. Nothing here can prove that nobody else can get in. Access can live outside the server (cloud console, domain, code host, a hosting panel), and data they already copied can't be taken back; changing secrets only stops the old ones from working.
Neither this guide nor OpsMate can tell you whether someone misused their access. For that, see the section above and get professional help.
An AI summary is a starting point. Check it against the raw output.
If you lock yourself out of SSH, OpsMate can't reach the server either. Use your cloud provider's web console to get back in.
Illustrative example
Illustrative example (not a real customer case): a small online shop's VPS was set up by a contractor whose contract ended last month. Step 2 shows two accounts besides root: deploy, which the shop's app runs as, and mike, which is in the sudo group. deploy's key file has three keys: the owner's, one labeled mike@macbook and one with no label; root's key file also has mike@macbook. last shows mike's most recent login three weeks before the contract ended, so nothing points to use after he left. The owner adds her own key to deploy, logs in from a new window and gets root from sudo whoami. ps -u mike shows nothing running, but sudo crontab -l -u mike shows a nightly database backup that writes to /home/mike/backups, and it is the shop's only backup. So, with help, the backup job is moved to deploy first, where it keeps running after the lock. Then she archives mike's home folder, locks the account with usermod -L -e 1, removes mike@macbook from both key files and, since nobody claims the unlabeled key, removes that one too, keeping copies. The names-only check on the app's .env shows DB_PASSWORD, PAYMENT_API_KEY, SMTP_PASSWORD and AWS_ACCESS_KEY_ID; each is replaced in turn: new value, update, restart, test a checkout, then turn off the old one. Two weeks later, with nothing broken, she deletes the account. What this does not prove: that no copy of the shop's data left with the contractor. The new secrets stop the old ones from working; they can't undo a copy.
AI diagnostic prompt
A contractor who had access to this server has left, and I'm taking back their access. Without changing anything, list the accounts that have a login shell, the members of the sudo, wheel and admin groups, the rules in /etc/sudoers and /etc/sudoers.d, every authorized_keys file with its last-changed date and each key's fingerprint and label, the recent logins of THEIR_USER, programs running as THEIR_USER, THEIR_USER's crontab, and any cron files or systemd services and timers that mention THEIR_USER or their home folder. For config and .env files, list setting names only and never print secret values. For commands that need sudo, list the exact commands for me to run rather than running them. Separate confirmed facts from guesses and say what is missing. Do not lock, delete or modify any account, key, password, crontab or service. If a change is needed, propose the smallest step with its backup, how to check it worked and how to undo it, and wait for my approval. If you see signs of misuse, say so and stop.
Next checks
Just took over the server and not sure what runs on it? Inherited a server: what to check first when you have no ops hire.
Something looked wrong? Was my Linux server hacked? Read-only checks for suspicious logins and processes.
Decide who may change the server from now on, and keep a routine check: Small-team VPS monitoring checklist.
Try OpsMate
500 free AI calls per month and unlimited servers. The desktop app keeps your SSH credentials on your own machine by default.
Need help interpreting the evidence?
OpsMate helps developers and operators investigate with AI. Review the evidence. After you click Analyze, the command output is sent to cloud AI for analysis; redact sensitive information first.