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:

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.

WhereWhat to look forWhat 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 themRemove their user, change the shared password, delete their access keys, turn on two-factor login
Domain registrar and DNSSame as aboveSame as above
Hosting control panel or web terminal, if you use oneA panel user or shared panel passwordRemove their user or change the password
The server itself (SSH)Accounts, keys, admin rightsSteps 2 to 4 below
Code repository and deploy pipelineTheir membership, deploy keys, tokens, pipeline secretsRemove them; replace keys and tokens (step 5)
DatabaseTheir own database account, the app's password they sawStep 5
Other services (email sending, payments, SMS, file storage, backups, monitoring)Their user, API keys they handledRemove their user; replace the keys (step 5)
Password manager, shared documents, team chatPasswords posted or shared with themRemove them; treat anything they saw as known

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/

Read-only. Passwords and SSH settings:

sudo passwd -S THEIR_USER
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|authorizedkeysfile) '

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

Read-only. Recent logins:

who
last -a -n 20 THEIR_USER
lslogins THEIR_USER

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.

  1. Keep your current session open in one window for the whole process.

  2. 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.

  3. 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_IP

    This appends your public key to YOUR_USER's authorized_keys file on the server. If ssh-copy-id isn't available (for example on Windows), paste the contents of the .pub file as a new line in that file instead. Undo: delete the line ending in yourname-laptop from that file (step 4 shows how to delete one line).

  4. 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_USER

    adduser creates the account and asks you to set its password (Debian and Ubuntu; on Red Hat-family systems use sudo useradd -m YOUR_NEW_USER and then sudo passwd YOUR_NEW_USER). usermod -aG sudo adds it to the admin group (use wheel instead of sudo on Red Hat-family systems). Undo: sudo userdel -r YOUR_NEW_USER removes the account and its home folder.

  5. Test from a new window, leaving the old one open:

    ssh YOUR_USER@SERVER_IP
    sudo whoami

    The 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.

  6. 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

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

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.

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

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

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:

SecretChange it inThen update
Database passwordsThe databaseThe app's config (.env or similar), then restart the app
App secrets (session or signing secrets, internal API keys)The app's configRestart the app; a new session secret usually logs all users out
Cloud access keysThe 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 tokensYour code host and deploy pipeline settingsThe server's code-pulling setup and pipeline secrets
Third-party API keys (email, payments, SMS)Each provider's dashboardThe app's config, then restart
Your own passwords they saw (cloud, domain, panel, email)Each websiteTurn 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

How to read what you see:

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

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

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:

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

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

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.

Start free Desktop with local credentials

Troubleshooting guides