Skip to content
Resources · Runbooks

Technical runbooks: the playbooks our engineers work from

These are condensed versions of procedures our engineers follow. They're written for people who administer systems, so they include the commands and the order that matters. Test them somewhere that isn't production first. Your environment will differ from ours in ways that matter.

Terminal output on a laptop screen during a server maintenance task

/ Runbooks on this page

01

Harden an AlmaLinux VPS before it goes live

02

Put Nginx in front of a web application

03

Roll out Conditional Access without lockouts

04

Offboard a Microsoft 365 user in the first hour

05

Deploy a Next.js app on a VPS with PM2 and Nginx

/ Direct answer

What is a technical runbook?

A technical runbook is a step-by-step procedure for one operational task, written so a competent engineer who didn't write it can carry it out and get the same result. Good runbooks list prerequisites, give the exact steps in order, explain how to check each one worked, and include a rollback. They turn one person's knowledge into the team's.

  • One task per runbook
  • Verify after every step
  • Rollback written in advance

/ 01

How do you harden an AlmaLinux VPS before it goes live?

Update the system, create a named admin user with SSH keys, disable root and password logins, limit the firewall to the ports you need, keep SELinux enforcing, turn on automatic security updates and add brute-force protection. Do it before the application is deployed, and test each change from a second SSH session so a mistake can't lock you out.

Runbook 01 · written for AlmaLinux 9, run as root
# 1. Patch, and create a named admin user
dnf -y upgrade
useradd -m -G wheel deploy
passwd deploy                     # sudo will ask for this
mkdir -p /home/deploy/.ssh && chmod 700 /home/deploy/.ssh
# paste your public key into /home/deploy/.ssh/authorized_keys, then:
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh

# 2. SSH: keys only, no root
cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
EOF
sshd -t && systemctl reload sshd

# 3. Firewall: web ports only
firewall-cmd --permanent --remove-service=cockpit
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload

# 4. SELinux stays on
getenforce                        # expect: Enforcing

# 5. Automatic security updates
dnf -y install dnf-automatic
sed -i 's/^upgrade_type.*/upgrade_type = security/; s/^apply_updates.*/apply_updates = yes/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timer

# 6. Brute-force protection
dnf -y install epel-release && dnf -y install fail2ban
printf '[sshd]\nenabled = true\n' > /etc/fail2ban/jail.d/sshd.local
systemctl enable --now fail2ban

Keep your original root session open, and confirm you can log in as the new user and use sudo from a second terminal before you close it.

/ 02

How do you put Nginx in front of a web application?

Install Nginx, bind your application to 127.0.0.1 so it can't be reached directly, add a server block that proxies to it with the right forwarding headers, allow the SELinux boolean for proxying, then issue a Let's Encrypt certificate. Test the configuration before every reload, so a typo never takes the site down.

Check the app itself is listening on 127.0.0.1 and not 0.0.0.0. If it listens on every interface and the firewall is ever loosened, the app becomes reachable without going through Nginx and its protections.

Runbook 02 · AlmaLinux 9
dnf -y install nginx
systemctl enable --now nginx

# Let Nginx connect to the local app port under SELinux
setsebool -P httpd_can_network_connect 1

# /etc/nginx/conf.d/app.conf
server {
    listen 80;
    server_name app.example.com;
    server_tokens off;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

nginx -t && systemctl reload nginx

# TLS from Let's Encrypt (certbot comes from EPEL)
dnf -y install certbot python3-certbot-nginx
certbot --nginx -d app.example.com
systemctl enable --now certbot-renew.timer

/ 03

How do you roll out Conditional Access without locking anyone out?

Create two emergency access accounts first and exclude them from every policy. Build each policy in report-only mode, watch the sign-in logs for one to two weeks, fix whatever would have been blocked, then enforce for a pilot group before everyone else. Document every exclusion with an owner and an expiry date.

01

Emergency access accounts

Two cloud-only accounts on the .onmicrosoft.com domain with Global Administrator, FIDO2 or certificate credentials stored separately, and an alert on any sign-in.

02

Policies in report-only

Require MFA for all users. Block legacy authentication. Require phishing-resistant MFA for admin roles. Exclude the emergency accounts from each.

03

Review the results

Filter the sign-in logs by report-only outcome. Every "would have failed" is a user, device or app to fix before enforcement.

04

Pilot

Enforce for IT and a volunteer group. Keep the rest in report-only.

05

Roll out in stages

Team by team, with instructions sent ahead of each stage and someone on hand to help.

06

Maintain

Review exclusions every quarter. An exclusion with no owner gets removed.

Microsoft now requires MFA to sign in to its admin portals whatever your own policies say, so emergency access accounts need an MFA method too. Use a FIDO2 key or certificate, not a phone that could leave with one person.

/ 04

How do you offboard a Microsoft 365 user in the first hour?

Disable the account and revoke sessions first, because a disabled account can otherwise keep using tokens it already holds. Then remove MFA methods and admin roles, convert the mailbox to shared and give the manager access, hand OneDrive to the manager, wipe or recover devices, and remove licences last, once the data is safe.

OrderStepWhy it's in this position
1Disable sign-in, revoke sessionsStops access now, not at the next token refresh
2Remove MFA methods and admin rolesCloses any way back in
3Mailbox to shared, grant manager accessKeeps the mail; shared mailboxes don't need a licence
4OneDrive access to the managerFiles stay reachable once the account is gone
5Wipe or recover devicesCompany data leaves with no one
6Remove licences, log completionLast, because removing a licence starts deleting user data
Runbook 04 · Microsoft Graph and Exchange Online PowerShell
Connect-MgGraph -Scopes "User.ReadWrite.All","User.RevokeSessions.All"
$upn = "leaver@contoso.com"

Update-MgUser -UserId $upn -AccountEnabled:$false   # block new sign-ins
Revoke-MgUserSignInSession -UserId $upn             # invalidate existing sessions

# Keep the mail before touching licences
Connect-ExchangeOnline
Set-Mailbox -Identity $upn -Type Shared
Add-MailboxPermission -Identity $upn -User "manager@contoso.com" -AccessRights FullAccess

/ 05

How do you deploy a Next.js app on a VPS with PM2 and Nginx?

Clone the repository as the deploy user, put the production environment file in place, install dependencies and build, then run the app with PM2 on 127.0.0.1 and register PM2 to start on boot. Nginx from runbook 02 sits in front. Later deploys are a pull, a build and a PM2 reload. This is the setup this website runs on.

Runbook 05 · as the deploy user
git clone git@github.com:your-org/your-app.git /var/www/app
cd /var/www/app
# .env.production goes here BEFORE building: NEXT_PUBLIC_ values are baked in at build time
npm ci
npm run build

# Run on localhost only, and restart on boot
sudo npm install -g pm2
pm2 start npm --name app -- start -- -p 3000 -H 127.0.0.1
pm2 save
pm2 startup systemd          # run the command it prints, once

# Every later deploy
git pull && npm ci && npm run build && pm2 reload app

In PM2's default fork mode a reload is a quick restart, so expect a second or two of interruption. Build before reloading, never after, so a failed build leaves the running version untouched.

/ 06

How should you write your own runbooks?

One task per runbook, written for someone competent who has never seen your environment. Start with the purpose and prerequisites, number every step, say how to check each one worked, include the rollback, and put a "last tested" date at the top. Treat a runbook nobody has run in a year as a draft.

Frequently Asked Questions

Yes, adapt them to your environment. Test somewhere that isn't production first, and replace the example domains, users and ports with your own.

Both are good choices. AlmaLinux suits teams that want Red Hat compatibility, SELinux by default and long support lifecycles. The hardening principles apply to either; the package and firewall commands differ.

No. Client-specific runbooks stay private because they describe real environments. The ones here are general procedures we're happy to share.

They're still here. The blog covers news and explainers for business readers, and the resources section has buyer guides and checklists. Runbooks are the hands-on engineering layer underneath.

Yes. Server builds and hardening sit under custom VPS deployments. Conditional Access is part of Entra ID identity management, and offboarding is one of the first things we automate in workflow automation projects.

/ Next step

Want this reviewed against your own environment?

Share your users, tools and the problem you are trying to solve. We will tell you plainly whether this service fits, and what we would look at first.

Get Free IT Assessment