Production engineering guide for automated compliance checks covering patterns, implementation strategies, and operational best practices.
Automated compliance checks ensure that systems, configurations, and processes consistently meet predefined standards—security policies, operational guidelines, and regulatory requirements—by executing repeatable, machine-driven validation across infrastructure, services, and workflows. These checks run unattended, either on a schedule, in response to changes, or as part of deployment pipelines, and trigger alerts or actions when deviations occur. They are essential in environments where drift, configuration inconsistency, or audit readiness are critical concerns, such as in regulated industries, cloud-native platforms, or high-velocity engineering teams.
The core task is to verify that a system’s current state matches a desired configuration. This involves comparing actual values—filesystem permissions, service status, network rules, runtime environment variables—with a set of expected values defined in a policy.
A policy is a declarative specification that defines what must be true. It consists of a set of rules, each with a condition and an expected outcome. For example:
- name: Ensure SSH is enabled and restricted
rules:
- path: /etc/ssh/sshd_config
key: PermitRootLogin
expected: "no"
description: "Root login via SSH must be disabled"
- path: /etc/ssh/sshd_config
key: PasswordAuthentication
expected: "no"
description: "Password authentication must be disabled"
- service: sshd
state: running
enabled: true
description: "SSH service must be running and enabled on boot"
A compliance checker reads this policy and applies it to a target system. The checker evaluates every rule, compares actual values against the expected ones, and produces a result per rule: pass, fail, or warning.
To validate a host, the checker executes commands locally or over SSH. It collects actual data and checks it against the policy.
On a Linux host, a minimal compliance check may use systemctl, grep, awk, and test in a shell script:
#!/bin/sh
POLICY_FILE="/etc/compliance/policy.yaml"
OUTPUT_DIR="/var/log/compliance"
# Load policy
policy() {
local key="$1"
yq -r ".rules[] | select(.key == \"$key\") | .expected" "$POLICY_FILE"
}
# Check SSH config
check_ssh_config() {
local expected=$(policy "PermitRootLogin")
local actual=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}')
if [ "$actual" = "$expected" ]; then
echo "PASS: PermitRootLogin = $actual"
else
echo "FAIL: Expected $expected, got $actual"
return 1
fi
}
# Check SSH service
check_ssh_service() {
local expected_state=$(policy "state")
local expected_enabled=$(policy "enabled")
local actual_state=$(systemctl is-active sshd)
local actual_enabled=$(systemctl is-enabled sshd)
if [ "$actual_state" = "$expected_state" ] && [ "$actual_enabled" = "$expected_enabled" ]; then
echo "PASS: Service state and enabled match"
else
echo "FAIL: Expected state=$expected_state, enabled=$expected_enabled; got state=$actual_state, enabled=$actual_enabled"
return 1
fi
}
# Main execution
for rule in $(yq -r '.rules[].key' "$POLICY_FILE"); do
check_ssh_config "$rule"
done
check_ssh_service
# Write results to JSON
{
"timestamp": "$(date -Iseconds)",
"host": "$(hostname)",
"checks": [
{
"rule": "PermitRootLogin",
"status": "pass",
"actual": "$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}')",
"expected": "$(policy "PermitRootLogin")"
},
{
"rule": "PasswordAuthentication",
"status": "fail",
"actual": "$(grep "^PasswordAuthentication" /etc/ssh/sshd_config | awk '{print $2}')",
"expected": "$(policy "PasswordAuthentication")"
}
]
} | jq -c . > "$OUTPUT_DIR/results-$(date +%s).json"
When this script runs, it outputs structured results. A failure in PasswordAuthentication is immediately visible in logs and alerting systems. The jq -c output is consumed by monitoring tools and CI/CD pipelines.
Drift—differences between expected and actual states—must be detected and reported. A compliance runner maintains a history of checks, comparing current results with previous ones.
On a CI system, a job runs check-compliance.sh and uploads results to a central store:
# In CI pipeline
git clone https://github.com/org/compliance-policies.git /tmp/policies
./check-compliance.sh
curl -X POST \
-H "Content-Type: application/json" \
-d @/var/log/compliance/results-1712345678.json \
https://compliance-api.example.com/v1/results
The API expects a JSON payload with a timestamp, host, and an array of checks. Each check has rule, status, actual, expected, and optional details.
If a check fails, the runner marks the entire compliance check as failed and sends a webhook to a notification system:
{
"event": "compliance-check-failed",
"timestamp": "2024-04-05T12:34:56Z",
"host": "web-01.prod.example.com",
"policy": "prod-ssh-standards.yaml",
"results": [
{
"rule": "PasswordAuthentication",
"status": "fail",
"actual": "yes",
"expected": "no",
"details": "Config file has PasswordAuthentication yes, but policy requires no."
}
],
"summary": "1 of 3 rules failed"
}
The details field is critical: it captures the full context of a failure, such as the actual file content, command output, or system logs, and is used for debugging.
Compliance checks are not just passive validations—they are enforcers. They can block workflows, trigger remediation, or require approvals.
In a deployment pipeline, a step runs a compliance check before promoting a service to production. If any rule fails, the pipeline halts.
The pipeline uses curl and jq to evaluate results and conditionally fail:
# After deploying a new version
COMPLIANCE_RESULT=$(curl -s https://compliance-api.example.com/v1/results/latest?host=web-01)
# Extract all failed checks
FAILED_CHECKS=$(echo "$COMPLIANCE_RESULT" | jq -c 'select(.results[].status == "fail") | .results[]')
if [ -n "$FAILED_CHECKS" ]; then
echo "Compliance checks failed:"
echo "$FAILED_CHECKS" | jq -r '.rule + ": " + .actual + " (expected: " + .expected + ")"'
exit 1
fi
The exit 1 causes the CI/CD system to mark the build as failed. The jq -r output is logged and visible in the deployment dashboard.
When compliance fails, the system can automatically apply fixes—remediation. Remediation is a set of actions that bring a system back into compliance.
A remediation script applies changes based on the policy and the actual state:
#!/bin/sh
# apply-remediation.sh
POLICY_FILE="/etc/compliance/policy.yaml"
RESULTS_FILE="/var/log/compliance/results.json"
# Read failures
failed_rules=$(jq -r '.results[] | select(.status == "fail") | .rule' "$RESULTS_FILE")
for rule in $failed_rules; do
case "$rule" in
"PermitRootLogin")
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
echo "Remediated: PermitRootLogin = no"
;;
"PasswordAuthentication")
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
echo "Remediated: PasswordAuthentication = no"
;;
*)
echo "Unknown rule: $rule, skipping remediation"
;;
esac
done
# Re-run compliance to verify fixes
./check-compliance.sh
This script uses sed to modify configuration files, systemctl to restart services, and re-runs the full check. It is idempotent: running it multiple times does not introduce side effects.
Remediation can be triggered manually or automatically. A cron job runs it hourly:
# /etc/cron.d/compliance-remediation
0 * * * * root /opt/compliance/apply-remediation.sh
If the remediation fails, the pipeline receives a compliance-remediation-failed event and sends an alert.
Compliance checks are not one-off tasks. They must run continuously and produce trends over time.
A monitoring system ingests results and plots compliance status per host and per rule. It detects drift by comparing current results to historical ones.
Each check result includes a timestamp and host. The system queries for results over time:
SELECT
host,
rule,
COUNT(*) AS total_checks,
SUM(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) AS passes,
SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END) AS failures,
AVG(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) AS pass_rate
FROM compliance_results
WHERE timestamp >= NOW() - INTERVAL '7 days'
GROUP BY host, rule
ORDER BY pass_rate ASC;
This query identifies hosts where PasswordAuthentication has been failing for multiple days, indicating a systemic issue.
Several subtle issues often go unnoticed:
PasswordAuthentication, but the key is missing entirely in the config file. The checker must handle not found as a distinct status. A grep may return nothing, but a jq expression or awk script must detect this.PermitRootLogin vs permitrootlogin in a config file. The checker must normalize keys, either via tr, awk, or yq.AllowedUsers: admin,ops. The checker must parse the value and validate each item.PasswordAuthentication yes, but yes appears as Yes or YES. The checker must use case or grep -i.sshd to be active for at least 30 seconds after restart. The runner must track systemctl status sshd over time and record active since timestamps.disk_usage /var/log > 80%. The checker must calculate the percentage and compare it to a threshold, not just a raw value.stderr in the result.version field. The checker logs which version was used, and the system tracks compliance drift per version.Compliance data is valuable beyond individual checks. Aggregated reports show compliance trends across the infrastructure.
A weekly report generates a summary:
#!/bin/sh
# generate-compliance-report.sh
OUTPUT_DIR="/var/reports/compliance"
DATE=$(date +%Y-%m-%d)
# Fetch all results from last 7 days
curl -s https://compliance-api.example.com/v1/results?since=7d | jq -c . > "$OUTPUT_DIR/results-$DATE.json"
# Compute pass rate per host
jq -r '
.results[] |
select(.timestamp > "2024-04-01T00:00:00Z") |
{host, status, rule} |
group_by(.host) |
map(
{
host: .[0].host,
total: length,
passed: [ .[] | select(.status == "pass") | .rule ] | length,
failed: [ .[] | select(.status == "fail") | .rule ] | length,
pass_rate: (.[].passed / .[].total * 100) | round(2)
}
) |
sort_by(.pass_rate) |
.[] |
"\(.host): \(.pass_rate)% pass rate (\(.passed)/\(.total))"
' "$OUTPUT_DIR/results-$DATE.json"
This report is sent via email or displayed in a dashboard. It reveals which hosts are lagging, which policies are most frequently violated, and which teams are responsible for which compliance gaps.
Compliance checks are not just about checking boxes. They are a continuous, self-correcting process where every failure is an opportunity to improve, and every pass is a signal of reliability.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy