Three properties make osquery durable where plenty of other agents have churned or outright disappeared:
- SQL is the API. Every table — processes, listening_ports, users, deb_packages, shell_history, kernel_modules — is queryable with standard
SELECT, JOIN, and WHERE. There's no DSL to learn, which matters more than people admit.
- One agent, many platforms. The same query runs on Ubuntu, RHEL, Debian, Amazon Linux, Alpine (via extensions), macOS, and Windows. That's genuinely rare for Linux-centric tooling.
- Open, inspectable, airgap-friendly. osquery is Apache-2.0 licensed, signed releases since 5.14 use a Microsoft Azure-issued signing key, and the daemon phones home nowhere by default.
The trade-off? osquery isn't a kernel-resident agent. A root-level attacker can tamper with osqueryd — full stop. Pair it with eBPF-based runtime detection (Falco or Tetragon) when you need tamper-resistance. Our Falco vs Tetragon comparison digs into that layer.
Architecture: How osquery Sees Your System
osquery ships three binaries:
osqueryi — an interactive REPL for ad-hoc queries. Your first stop for learning (and, frankly, the most fun part).
osqueryd — the long-running daemon that executes scheduled query packs and streams results to configured logger plugins.
osqueryctl — a small helper for starting, stopping, and configuring the daemon.
Under the hood, tables are backed by virtual tables implemented in C++ or loaded as extensions. Each one reads from a specific OS subsystem: /proc, Netlink, the Audit framework, BPF programs, D-Bus, and so on. Queries get parsed by SQLite and dispatched to the relevant table implementations.
On Linux, osquery can power its process_events and socket_events tables from three sources:
- Audit subsystem (classic, stable; conflicts with
auditd)
- BPF (enabled with
--enable_bpf_events=true; low overhead, non-conflicting)
- OpenBSM / EndpointSecurity (macOS only)
For new deployments on modern kernels (5.8+), BPF is the right default. I'd honestly skip the Audit backend entirely unless you have a specific compliance reason not to.
Installing osquery 5.22 on Linux
Use the official repository. Avoid third-party builds — the packages are signed, and the repo is mirrored on GitHub releases for airgapped environments.
Debian and Ubuntu (22.04, 24.04, 26.04)
# Add the osquery signing key and repository
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://pkg.osquery.io/deb/pubkey.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/osquery.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/osquery.gpg] https://pkg.osquery.io/deb deb main" \
| sudo tee /etc/apt/sources.list.d/osquery.list
sudo apt-get update
sudo apt-get install -y osquery
osqueryi --version
# osqueryi version 5.22.1
RHEL, AlmaLinux, Rocky, Fedora
sudo rpm --import https://pkg.osquery.io/rpm/GPG
sudo tee /etc/yum.repos.d/osquery-s3.repo <<'EOF'
[osquery-s3-rpm]
name=osquery RPM repository
baseurl=https://pkg.osquery.io/rpm
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://pkg.osquery.io/rpm/GPG
EOF
sudo dnf install -y osquery
Minimal production configuration
Drop this into /etc/osquery/osquery.conf. It enables BPF-backed events, sets sane logging, and schedules a couple of high-value queries to get you started.
{
"options": {
"config_plugin": "filesystem",
"logger_plugin": "filesystem",
"logger_path": "/var/log/osquery",
"disable_logging": "false",
"log_result_events": "true",
"schedule_splay_percent": "10",
"pidfile": "/var/osquery/osquery.pidfile",
"events_expiry": "3600",
"database_path": "/var/osquery/osquery.db",
"verbose": "false",
"worker_threads": "2",
"enable_monitor": "true",
"disable_events": "false",
"enable_bpf_events": "true",
"audit_allow_process_events": "false",
"audit_allow_sockets": "false"
},
"schedule": {
"process_events_bpf": {
"query": "SELECT pid, path, cmdline, cwd, auid, uid, gid, parent, time FROM process_events;",
"interval": 60,
"removed": false
},
"listening_ports_snapshot": {
"query": "SELECT pid, port, protocol, address, path FROM listening_ports WHERE address NOT IN ('127.0.0.1','::1');",
"interval": 300,
"snapshot": true
}
},
"packs": {
"incident-response": "/opt/osquery/packs/incident-response.conf",
"vuln-management": "/opt/osquery/packs/vuln-management.conf",
"hardware-monitoring": "/opt/osquery/packs/hardware-monitoring.conf"
}
}
Enable and start the daemon:
sudo systemctl enable --now osqueryd
sudo systemctl status osqueryd
sudo tail -f /var/log/osquery/osqueryd.results.log
Important: if auditd is already running and you plan to use the Audit backend, stop and mask it. Both osqueryd and auditd open the audit netlink socket and they will fight over it. With enable_bpf_events=true you can leave auditd alone — which is another reason BPF is the friendlier default.
The 20 osquery Queries Every Linux Security Engineer Should Know
Drop into osqueryi and try these. They map directly to MITRE ATT&CK techniques and cover the highest-value detection surfaces on Linux.
Persistence & initial access
-- T1053.003 Cron jobs (system + per-user)
SELECT * FROM crontab;
-- T1543.002 systemd services modified in last 7 days
SELECT name, path, source, user
FROM systemd_units
WHERE source LIKE '/etc/systemd/%'
AND (SELECT mtime FROM file WHERE path = systemd_units.source) > (strftime('%s','now') - 604800);
-- T1546.004 Shell init files touched by non-root
SELECT f.path, f.uid, f.mtime, u.username
FROM file f
JOIN users u ON f.uid = u.uid
WHERE f.path IN ('/etc/profile','/etc/bashrc','/etc/bash.bashrc')
OR f.path LIKE '/home/%/.bashrc'
OR f.path LIKE '/home/%/.profile'
OR f.path LIKE '/home/%/.bash_profile';
-- T1136 New local users in the last day
SELECT username, uid, gid, shell, directory
FROM users
WHERE uid >= 1000 AND uid < 65534;
Process and execution
-- T1059 Processes running from world-writable or tmp paths
SELECT p.pid, p.name, p.path, p.cmdline, p.cwd, u.username
FROM processes p
LEFT JOIN users u ON p.uid = u.uid
WHERE p.path LIKE '/tmp/%'
OR p.path LIKE '/dev/shm/%'
OR p.path LIKE '/var/tmp/%';
-- T1055 Orphaned processes (parent died - possible injection / daemon)
SELECT pid, name, path, cmdline, parent
FROM processes
WHERE parent = 1 AND name NOT IN ('systemd','init','dbus-daemon');
-- T1562.001 Deleted binaries still executing (classic LD_PRELOAD / fileless)
SELECT pid, name, path
FROM processes
WHERE on_disk = 0;
Network and lateral movement
-- T1021 Outbound connections by non-system users
SELECT p.pid, p.name, p.cmdline,
pos.remote_address, pos.remote_port, u.username
FROM process_open_sockets pos
JOIN processes p ON p.pid = pos.pid
JOIN users u ON u.uid = p.uid
WHERE pos.remote_address NOT IN ('','0.0.0.0','::','127.0.0.1','::1')
AND pos.family IN (2, 10)
AND u.uid >= 1000;
-- T1571 Listening ports not bound to loopback
SELECT pid, port, protocol, address, path
FROM listening_ports
WHERE address NOT IN ('127.0.0.1','::1');
Credential access
-- T1552.003 Readable shell history with secrets-like strings
SELECT username, history_file, command
FROM shell_history
WHERE command LIKE '%AWS_SECRET_ACCESS_KEY%'
OR command LIKE '%BEGIN RSA PRIVATE KEY%'
OR command REGEXP '(api[_-]?key|token|password)\s*=';
-- T1552.004 World-readable private keys
SELECT path, (SELECT mode FROM file WHERE file.path = user_ssh_keys.path) AS mode,
uid, encrypted
FROM user_ssh_keys
WHERE encrypted = 0;
Defense evasion & rootkits
-- T1547.006 Non-stock kernel modules
SELECT name, size, used_by, status, address
FROM kernel_modules
WHERE name NOT IN (SELECT name FROM kernel_info); -- refine per distro baseline
-- T1014 Preloaded libraries (LD_PRELOAD persistence)
SELECT * FROM process_envs WHERE key IN ('LD_PRELOAD','LD_AUDIT','LD_LIBRARY_PATH');
-- Hidden files in /etc or /var
SELECT path FROM file
WHERE (path LIKE '/etc/.%' OR path LIKE '/var/.%')
AND type = 'regular';
Integrity & package state
-- Packages installed outside the package manager (Debian)
SELECT * FROM deb_packages WHERE source = '';
-- Binaries on PATH whose hash changed in last 24h
SELECT f.path, f.mtime, h.sha256
FROM file f
JOIN hash h ON h.path = f.path
WHERE f.path IN ('/usr/bin/sshd','/usr/sbin/sshd','/usr/bin/sudo','/usr/bin/su','/bin/login')
AND f.mtime > (strftime('%s','now') - 86400);
Those thirteen queries already cover a meaningful slice of the ATT&CK Linux matrix. Save them into query packs so they run on a schedule across the fleet, not as one-off curiosities.
Building Query Packs
A pack is a versioned JSON file that groups related queries, each with its own interval, platform filter, and ATT&CK annotation. Here's a lean incident-response pack:
{
"platform": "linux",
"version": "1.6.0",
"queries": {
"suid_binaries_non_stock": {
"query": "SELECT f.path, f.mode, f.uid, h.sha256 FROM file f JOIN hash h ON h.path = f.path WHERE f.path LIKE '/usr/bin/%' AND (f.mode LIKE '%4755%' OR f.mode LIKE '%6755%') AND f.path NOT IN ('/usr/bin/sudo','/usr/bin/passwd','/usr/bin/chsh','/usr/bin/chfn','/usr/bin/gpasswd','/usr/bin/newgrp','/usr/bin/mount','/usr/bin/umount','/usr/bin/su','/usr/bin/pkexec');",
"interval": 3600,
"description": "Non-baseline SUID binaries",
"snapshot": true
},
"rogue_containers": {
"query": "SELECT id, name, image, command, created, state, status FROM docker_containers WHERE state='running' AND privileged=1;",
"interval": 600,
"description": "Privileged running Docker containers"
},
"rogue_listeners": {
"query": "SELECT p.name, p.path, lp.port, lp.address, lp.protocol FROM listening_ports lp JOIN processes p ON p.pid = lp.pid WHERE lp.address NOT IN ('127.0.0.1','::1') AND p.name NOT IN ('sshd','nginx','systemd-resolve','chronyd');",
"interval": 300,
"description": "Unexpected network listeners"
},
"auth_failures_burst": {
"query": "SELECT username, path, time FROM last WHERE type=6 AND time > (strftime('%s','now') - 3600);",
"interval": 300,
"description": "Failed logins in the last hour"
}
}
}
Reference the pack in osquery.conf via the packs key. Community-maintained packs — ChainGuard's threat-hunting queries, the official osquery-defense pack, and AWS's aws-linux-baseline — are excellent starting material. Fork them, trim the parts that don't apply to you, and adapt.
Managing osquery at Scale with FleetDM
Running osqueryd on one box is useful. Running it on 500 is where FleetDM earns its keep. Fleet is an open-source (MIT) control plane that distributes configs, runs live queries, schedules packs, and aggregates results — basically the glue you'd otherwise end up writing yourself.
Deploying FleetDM on Linux
Fleet needs MySQL 8.0+ and Redis 6+. Here's a minimal single-node deployment with Docker Compose:
mkdir -p /opt/fleet && cd /opt/fleet
cat > docker-compose.yml <<'EOF'
services:
mysql:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: fleet
MYSQL_USER: fleet
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes: [ "mysql:/var/lib/mysql" ]
command: --default-authentication-plugin=caching_sha2_password
redis:
image: redis:7-alpine
volumes: [ "redis:/data" ]
fleet:
image: fleetdm/fleet:latest
depends_on: [ mysql, redis ]
environment:
FLEET_MYSQL_ADDRESS: mysql:3306
FLEET_MYSQL_DATABASE: fleet
FLEET_MYSQL_USERNAME: fleet
FLEET_MYSQL_PASSWORD: ${MYSQL_PASSWORD}
FLEET_REDIS_ADDRESS: redis:6379
FLEET_SERVER_TLS: "true"
FLEET_SERVER_CERT: /certs/fleet.crt
FLEET_SERVER_KEY: /certs/fleet.key
ports: [ "8080:8080" ]
volumes: [ "./certs:/certs:ro" ]
volumes:
mysql: {}
redis: {}
EOF
# One-time DB prep
docker compose run --rm fleet fleet prepare db
docker compose up -d
Initialize the first admin at https://fleet.example.com:8080, then generate an enroll secret and push the Fleet-issued osquery.flags and secret file to each host. The fleetctl CLI makes this declarative, which is where it really starts to shine:
# Install fleetctl
curl -L https://github.com/fleetdm/fleet/releases/latest/download/fleetctl_linux.tar.gz | tar xz
sudo install fleetctl /usr/local/bin/
fleetctl config set --address https://fleet.example.com:8080
fleetctl login --email [email protected]
# Apply a query pack as code
cat > incident-response.yml <<'EOF'
apiVersion: v1
kind: pack
spec:
name: incident-response
targets:
labels: [ "All Linux Hosts" ]
queries:
- query: suid_binaries_non_stock
interval: 3600
snapshot: true
EOF
fleetctl apply -f incident-response.yml
Everything in Fleet — users, teams, labels, queries, packs, MDM profiles — is GitOps-able. Store the manifests in a repo, gate changes behind review, and apply with fleetctl apply from CI. Once you've done this once, going back to clicking through a web UI feels barbaric.
Live queries for incident response
When a breach alert fires, you need answers in seconds, not minutes. Fleet's live query feature fans a query out to matching hosts and streams results back in real time:
fleetctl query --hosts label:linux-prod \
--query "SELECT pid, name, path, cmdline FROM processes WHERE path LIKE '/tmp/%';"
This is the killer feature over pure file-based logging. During the 2024 XZ backdoor disclosure (remember that panic?), responders used nearly identical osquery live queries to find vulnerable liblzma versions across tens of thousands of hosts in minutes. Try doing that by SSH-ing into each box.
Shipping Results to a SIEM
Filesystem logging is fine on a laptop. In production you want every osqueryd.results.log line parsed, indexed, and correlated. Three paths dominate:
Option 1 — Elastic Stack via Filebeat
# /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: filestream
id: osquery-results
paths: [ /var/log/osquery/osqueryd.results.log ]
parsers:
- ndjson:
keys_under_root: true
add_error_key: true
processors:
- add_host_metadata: ~
- add_fields:
target: osquery
fields:
data_stream.dataset: osquery.results
output.elasticsearch:
hosts: [ "https://elastic.internal:9200" ]
api_key: "${ELASTIC_API_KEY}"
Elastic ships a prebuilt osquery integration with ECS-compliant dashboards and detection rules — install it from Kibana's Fleet UI and point Filebeat at it. That's it. You're done.
Option 2 — Splunk via the TA for osquery
Install the Splunk Technology Add-on for osquery, configure a monitor stanza on /var/log/osquery/, and the data lands as the osquery:results sourcetype. Splunk Security Content ships dozens of osquery-powered correlation rules (e.g., Linux Auditd Osquery Service Stop), so you can get detections live on day one.
Option 3 — Loki / Grafana for cost-sensitive shops
# promtail config fragment
scrape_configs:
- job_name: osquery
static_configs:
- targets: [ localhost ]
labels:
job: osquery
host: ${HOSTNAME}
__path__: /var/log/osquery/osqueryd.results.log
pipeline_stages:
- json:
expressions:
name: name
action: action
hostIdentifier: hostIdentifier
unixTime: unixTime
- timestamp:
source: unixTime
format: Unix
Loki's log-based metrics make it trivial to alert on something like rate of new SUID binaries per host per hour — a cheap and surprisingly effective detection.
osquery vs auditd: When to Use Which
This is the single most common question we get, and it deserves a straight answer. The honest truth is that they solve overlapping but not identical problems.
| Dimension | osquery | auditd |
| Interface | SQL over virtual tables | Rule-based audit.rules |
| Data shape | Point-in-time state + event streams | Syscall-level event stream |
| Cross-platform | Linux, macOS, Windows, FreeBSD | Linux only |
| Fleet management | Native (FleetDM) | Bring-your-own (Ansible + SIEM) |
| Forensic fidelity | Medium (BPF or Audit-backed) | High (raw syscall data, tamper-evident) |
| Compliance mapping | Easy (SQL + packs) | Strong for CIS / STIG rule IDs |
| Resource overhead | ~50–150 MB RSS, ~1–3% CPU | Low, but logs are voluminous |
| Conflict | Cannot share audit netlink with auditd | Dedicated audit subscriber |
Our recommendation for 2026:
- Use osquery with the BPF backend as your primary visibility and threat-hunting layer. It gives analysts an ergonomic SQL interface across the fleet.
- Keep auditd running for compliance when a regulator explicitly names it (STIG, PCI DSS) — but narrow the rule set ruthlessly and forward only the rule IDs that actually map to controls. A noisy auditd is nobody's friend.
- Add Falco or Tetragon for tamper-resistant, kernel-level runtime detection — a layer osquery simply can't match.
Hardening osquery Itself
osquery is a privileged daemon with read access to the entire system. Treat it accordingly.
1. Lock down the configuration and database
sudo chown -R root:root /etc/osquery /var/osquery /var/log/osquery
sudo chmod 0750 /etc/osquery /var/osquery
sudo chmod 0640 /etc/osquery/osquery.conf
sudo chmod 0750 /var/log/osquery
# Ensure the osquery user cannot read results logs; only root and your log shipper's group.
2. Restrict who can stop or reconfigure the service
On RHEL with SELinux, confine osqueryd in its own domain and only let the auditadm_r role stop it. On Ubuntu, use AppArmor profiles plus systemd's ProtectSystem=strict, NoNewPrivileges=true, and CapabilityBoundingSet=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_SYS_ADMIN. See our systemd service hardening guide for the full template.
3. Detect tampering with osquery from osquery
-- Alert if osqueryd was killed or restarted outside of package upgrades
SELECT name, path, start_time, parent FROM processes WHERE name = 'osqueryd';
-- Alert on config file edits
SELECT path, mtime, size FROM file
WHERE path = '/etc/osquery/osquery.conf'
AND mtime > (strftime('%s','now') - 3600);
Combine with a Falco rule for process killed or traced by non-root to catch the scenario where an attacker tries to stop osqueryd to clear their tracks. (Yes, they'll try. Assume they'll try.)
4. Verify package signatures
Since osquery 5.14, releases are signed with a Microsoft Azure-issued key. Your package manager already verifies this; do not bypass with --nogpgcheck or --allow-unauthenticated. I don't care how much of a hurry you're in.
Common Pitfalls and How to Avoid Them
- Leaving the audit backend and auditd both enabled. They fight over the netlink socket. Pick one. If you truly need both, run
auditd in auditd -n mode on a separate rule set and use enable_bpf_events=true for osquery.
- Scheduling
SELECT * FROM processes every 10 seconds. Processes is an expensive table. Use process_events (a delta stream) instead of snapshotting the entire process table every few seconds — your ops team will thank you.
- Ignoring
schedule_splay_percent. Without splay, every host in the fleet runs the same heavy query at the same second and brownouts your SIEM. Default to 10%.
- No baseline for SUID / kernel modules / systemd units. Detection queries like "non-stock SUID" are only useful when you know what stock is. Generate the baseline from a freshly built golden image, check it into git, and diff at query time.
- Shipping raw results without an index strategy. osquery results are verbose. In Elasticsearch, use ILM (hot/warm/cold) and keep only the tables you actually query for more than 7 days. Your infra bill will notice.
When osquery Is Not the Right Tool
Be honest with yourself about these cases:
- Sub-second detection latency. osquery events have an inherent buffer. For true real-time kernel enforcement, use Tetragon or a commercial EDR with kernel hooks.
- Blocking / prevention. osquery is read-only. It can detect a malicious process, but it can't kill it. Pair it with Falco-sidekick or a SOAR that can actually issue remediation.
- Ephemeral serverless / FaaS. A daemon with state doesn't fit AWS Lambda. Reach for CloudTrail, runtime sandboxing, and code signing instead.
- Hostile insiders with root. A sufficiently privileged attacker will disable or blind osquery. Assume compromise and put your tamper-detection somewhere they can't reach.
FAQ
Is osquery still maintained in 2026?
Yes. The Linux Foundation hosts the project, osquery 5.22.1 shipped in February 2026, and minor releases land roughly every two months. The project switched to a Microsoft Azure-issued signing key in 5.14, which put earlier concerns about release-key custody to bed.
Can osquery replace my commercial EDR?
For small-to-medium Linux fleets with a capable security team — yes, paired with Falco or Tetragon for kernel-level runtime detection and a SIEM for correlation. For larger environments, or teams that need 24/7 MDR, commercial EDR still wins on response automation and SOC workflows. Just not on raw visibility.
Does osquery need the internet to function?
No. osqueryd has zero outbound dependencies by default. Packages can be mirrored, and FleetDM deploys fully on-prem or airgapped. This is a frequent deciding factor for regulated and sovereign environments — and, honestly, a refreshing change from most modern security tools.
What's the performance overhead of osquery on a busy Linux server?
Typical production hosts see 50–150 MB RSS and 1–3% sustained CPU with the default pack set and BPF events enabled. Heavy scheduled queries (like hashing every file in /usr/bin hourly) can push this higher — profile with SELECT * FROM osquery_schedule;, which includes per-query execution stats.
Should I use FleetDM or Kolide K2?
Use FleetDM (self-hosted, MIT-licensed) when you need full data control, airgap support, or want to GitOps your endpoint management. Use Kolide K2 (SaaS) when you value end-user privacy transparency, Slack-based compliance nudges, and you'd rather outsource the infrastructure. For context: Kolide discontinued the original self-hosted Fleet repository back in 2019 — FleetDM is the actively maintained fork.
Can I use osquery for compliance audits (CIS, PCI, HIPAA)?
Yes. The community maintains osquery packs aligned to CIS Benchmarks for Ubuntu, RHEL, and Amazon Linux. For PCI and HIPAA, map each query to the relevant control ID in a pack metadata field, then export the results as evidence. Pair with OpenSCAP (covered in our CIS benchmark article) for full control attestation.
Wrap Up
osquery isn't new, but 2026 is a great time to deploy it. BPF-backed events, a mature FleetDM ecosystem, signed release keys, and broad SIEM integrations have closed the gaps that held back earlier adopters. Start with a handful of high-value packs, centralize with FleetDM, ship to your SIEM of choice, and pair with a kernel-level runtime layer for tamper resistance. The result? Observability that an auditor can read, an analyst can query, and an attacker can't trivially bypass. Hard to beat that combo.