← Back to posts
comparison guide self-hosted · · 10 min read

Self-Hosted Log Integrity & Tamper-Evident Audit Logging Guide 2026

Complete guide to self-hosted tamper-evident audit logging with Rekor, hash-chained logs, and cryptographic verification for compliance and security.

OS
Editorial Team

Maintaining trustworthy audit logs is critical for security investigations, regulatory compliance, and incident response. When log entries can be silently modified or deleted by an attacker who has gained system access, your entire audit trail becomes worthless. Tamper-evident logging ensures that any modification to historical log entries is immediately detectable through cryptographic verification.

This guide covers self-hosted solutions for building tamper-evident audit trails, from transparency log implementations to hash-chained logging systems that you can run entirely on your own infrastructure.

Why Self-Hosted Log Integrity Matters

Centralized logging services from cloud providers give you a convenient place to send your logs, but they come with inherent trust assumptions:

  • Cloud provider access: Provider administrators or compromised credentials can potentially alter log data
  • Data sovereignty: Regulations like GDPR, HIPAA, and SOC 2 often require audit data to remain under your direct control
  • Single point of failure: If your logging provider goes down, you lose visibility into your systems during the outage
  • Cost at scale: Continuous high-volume log ingestion to managed services becomes expensive quickly

Self-hosted tamper-evident logging gives you full control over your audit data while maintaining cryptographic guarantees that entries haven’t been altered after being written.

Understanding Tamper-Evident Logging

Tamper-evident logging uses cryptographic techniques to make unauthorized modifications detectable. The three primary approaches are:

ApproachHow It WorksStrengthsWeaknesses
Hash chainingEach log entry includes a hash of the previous entry, forming an unbreakable chainSimple to implement, fast verificationSingle chain can become a bottleneck
Merkle treesEntries are organized in a hash tree; any modification changes the root hashEfficient verification of subsets, parallel writesMore complex implementation
Transparency logsAppend-only public logs with cryptographic proofs (e.g., Rekor/Sigstore)Public verifiability, ecosystem integrationRequires more infrastructure

Rekor: Sigstore’s Transparency Log

Rekor is the transparency log component of the Sigstore project. While primarily designed for software supply chain security, its append-only, cryptographically verifiable architecture makes it suitable for general-purpose tamper-evident logging.

Key Features

  • Append-only ledger: Once an entry is written, it cannot be modified or deleted without detection
  • Merkle tree proofs: Each entry gets an inclusion proof that can be verified independently
  • Signed tree heads: Periodic signatures of the current tree state prevent rollback attacks
  • RESTful API: Simple HTTP interface for writing and querying log entries
  • Redis/MySQL backend: Flexible storage options for production deployments

Installation

The easiest way to run Rekor is via Docker Compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
version: '3.8'
services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  rekor-server:
    image: ghcr.io/sigstore/rekor/rekor-server:v1.3.8
    ports:
      - "3000:3000"
    environment:
      - REKOR_SERVER_SIGNER=file
      - REKOR_SERVER_SIGNER_FILE_PATH=/etc/rekor/signing-keys/ec_private.pem
      - REKOR_SERVER_TRILLIAN_LOG_SERVER=trillian-log-server:8090
      - REKOR_SERVER_REDIS_SERVER_ADDRESS=redis:6379
    volumes:
      - ./signing-keys:/etc/rekor/signing-keys
    depends_on:
      - redis

  trillian-log-server:
    image: gcr.io/trillian-opensource-ci/log_server:v1.5.2
    ports:
      - "8090:8090"
    environment:
      - MYSQL_URI=test:zaphod@tcp(mysql:3306)/test
    depends_on:
      - mysql

  trillian-log-signer:
    image: gcr.io/trillian-opensource-ci/log_signer:v1.5.2
    ports:
      - "8091:8091"
    environment:
      - MYSQL_URI=test:zaphod@tcp(mysql:3306)/test
    depends_on:
      - mysql

  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=test
      - MYSQL_USER=test
      - MYSQL_PASSWORD=zaphod
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  redis_data:
  mysql_data:

Save this as docker-compose.yml and start the stack:

1
2
mkdir -p signing-keys
docker compose up -d

Writing and Verifying Log Entries

Use the rekor-cli tool to interact with your transparency log:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Install rekor-cli
go install github.com/sigstore/rekor/cmd/rekor-cli@latest

# Write a log entry
rekor-cli upload --rekor_server http://localhost:3000 \
  --artifact /var/log/auth.log

# Verify an entry's inclusion proof
rekor-cli verify --rekor_server http://localhost:3000 \
  --uuid <entry-uuid>

# Check the signed tree head
rekor-cli loginfo --rekor_server http://localhost:3000

Programmatic Entry Creation

For automated log ingestion, use the REST API directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Create a hashedrekord entry (log a file hash)
curl -X POST http://localhost:3000/api/v1/log/entries \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "0.0.1",
    "kind": "hashedrekord",
    "spec": {
      "data": {
        "hash": {
          "algorithm": "sha256",
          "value": "<file-hash>"
        }
      },
      "signature": {
        "content": "<base64-signature>",
        "publicKey": {
          "content": "<base64-public-key>"
        }
      }
    }
  }'

Hash-Chained Audit Logs

For simpler use cases, hash-chained log files provide tamper evidence without the infrastructure overhead of a full transparency log server.

How Hash Chaining Works

Each log entry includes a cryptographic hash of the previous entry:

1
2
3
Entry 1: {timestamp: "2026-04-25T10:00:00Z", action: "login", prev_hash: null, hash: sha256(entry1)}
Entry 2: {timestamp: "2026-04-25T10:01:00Z", action: "sudo", prev_hash: sha256(entry1), hash: sha256(entry2)}
Entry 3: {timestamp: "2026-04-25T10:02:00Z", action: "logout", prev_hash: sha256(entry2), hash: sha256(entry3)}

If an attacker modifies Entry 1, the prev_hash in Entry 2 no longer matches, and the entire chain from that point forward becomes invalid.

Implementing Hash-Chained Logging in Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import hashlib
import json
import datetime

class HashChainedLogger:
    def __init__(self, log_file):
        self.log_file = log_file
        self.last_hash = None
        self._load_last_hash()

    def _load_last_hash(self):
        """Load the hash of the last entry from the log file."""
        try:
            with open(self.log_file, 'r') as f:
                lines = f.readlines()
                if lines:
                    last_entry = json.loads(lines[-1])
                    self.last_hash = last_entry['hash']
        except (FileNotFoundError, json.JSONDecodeError):
            self.last_hash = None

    def log_entry(self, action, details):
        """Append a new entry to the hash-chained log."""
        entry = {
            'timestamp': datetime.datetime.utcnow().isoformat() + 'Z',
            'action': action,
            'details': details,
            'prev_hash': self.last_hash,
        }
        entry_str = json.dumps(entry, sort_keys=True)
        entry['hash'] = hashlib.sha256(entry_str.encode()).hexdigest()
        self.last_hash = entry['hash']

        with open(self.log_file, 'a') as f:
            f.write(json.dumps(entry) + '\n')
        return entry['hash']

    def verify_chain(self):
        """Verify the entire hash chain for tampering."""
        with open(self.log_file, 'r') as f:
            lines = f.readlines()

        prev_hash = None
        for i, line in enumerate(lines):
            entry = json.loads(line)
            if entry['prev_hash'] != prev_hash:
                print(f"TAMPER DETECTED at entry {i+1}!")
                return False
            entry_str = json.dumps({
                'timestamp': entry['timestamp'],
                'action': entry['action'],
                'details': entry['details'],
                'prev_hash': entry['prev_hash'],
            }, sort_keys=True)
            expected_hash = hashlib.sha256(entry_str.encode()).hexdigest()
            if entry['hash'] != expected_hash:
                print(f"TAMPER DETECTED at entry {i+1} (hash mismatch)!")
                return False
            prev_hash = entry['hash']

        print(f"Chain verified: {len(lines)} entries, all valid.")
        return True

# Usage
logger = HashChainedLogger('/var/log/audit/chain.log')
logger.log_entry('user_login', {'user': 'admin', 'source_ip': '192.168.1.100'})
logger.log_entry('file_access', {'path': '/etc/shadow', 'action': 'read'})
logger.verify_chain()

Deploying with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
version: '3.8'
services:
  audit-logger:
    build: .
    volumes:
      - ./audit_logs:/var/log/audit
      - /var/log/syslog:/host/syslog:ro
    environment:
      - LOG_FILE=/var/log/audit/chain.log
      - WATCH_PATHS=/host/syslog,/var/log/auth.log
    restart: unless-stopped

Linux Auditd with Integrity Verification

The Linux Audit subsystem (auditd) provides kernel-level auditing that can be combined with hash-chained verification for a robust self-hosted integrity solution.

Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Install auditd
apt install auditd audispd-plugins -y

# Configure audit rules for critical events
cat >> /etc/audit/rules.d/integrity.rules << 'EOF'
# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity

# Monitor sudo usage
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers

# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd

# Monitor cron jobs
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron

# Monitor kernel module loading
-a always,exit -F arch=b64 -S init_module -S finit_module -k module_load
EOF

# Reload rules
auditctl -R /etc/audit/rules.d/integrity.rules

Forwarding Audit Logs with Integrity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Configure auditd to use a remote logging plugin
cat >> /etc/audit/audisp-remote.conf << 'EOF'
remote_server = log-integrity.internal
port = 60
transport = tcp
mode = immediate
EOF

# Enable the audit dispatcher
systemctl enable audispd
systemctl restart auditd

Enterprise Compliance Considerations

When deploying tamper-evident logging for compliance, consider these requirements:

StandardLog RetentionIntegrity RequirementAccess Control
SOC 21+ yearTamper-evident, cryptographicRole-based
HIPAA6 yearsImmutable audit trailLeast privilege
PCI DSS1 year (90 days online)File integrity monitoringSegregated access
GDPRAs neededIntegrity and confidentialityDocumented processing
SOX7 yearsTamper-proof, auditableSegregation of duties

Best Practices for Compliance

  1. Separate logging infrastructure: Run your log integrity systems on separate hosts from the systems being audited
  2. Write-once storage: Use WORM (Write Once, Read Many) storage or append-only volumes where possible
  3. Regular chain verification: Schedule automated integrity checks of hash chains at least daily
  4. Off-site backup: Maintain encrypted copies of log data in a separate physical location
  5. Access logging: Log all access to the logging infrastructure itself, creating a meta-audit trail

Choosing the Right Approach

CriteriaRekor/Transparency LogHash-Chained FilesAuditd + Forwarding
ComplexityHighLowMedium
ScalabilityHigh (distributed)Medium (single chain)High (kernel-level)
Public verifiabilityYesNoNo
Regulatory acceptanceEmergingAcceptedWidely accepted
Infrastructure needsRedis + MySQL + TrillianLocal disk onlyKernel support
Best forSupply chain, multi-orgSingle-team audit logsLinux system auditing

For most self-hosted environments, a layered approach works best: use auditd for kernel-level system auditing, hash-chained logging for application-level events, and Rekor for software supply chain artifacts and critical security events.

For related reading, see our supply chain security guide with Cosign and Notation and the runtime security comparison with Falco, osquery, and auditd. If you’re building a broader security operations center, our SIEM comparison covers centralized log analysis platforms.

FAQ

What is tamper-evident logging and how does it differ from tamper-proof logging?

Tamper-evident logging makes unauthorized modifications detectable through cryptographic verification (hash chains, Merkle proofs), while tamper-proof logging aims to make modifications technically impossible. Tamper-evident is more practical for self-hosted environments because it provides strong detection guarantees without requiring specialized hardware or append-only storage. If someone modifies a log entry, the cryptographic proof breaks and the tampering is immediately visible.

Can an attacker still delete log entries in a hash-chained system?

Yes, an attacker with filesystem access can delete log entries. However, this deletion is itself detectable: the hash chain will show a gap (the prev_hash of the entry after the deletion point won’t match any existing entry). Combined with periodic chain verification and off-site backups, deletions become both detectable and recoverable. For stronger guarantees, consider combining hash chains with write-once storage or remote log forwarding.

Is Rekor suitable for high-volume application logging?

Rekor is optimized for software supply chain artifact logging rather than high-volume application telemetry. For application-level tamper-evident logging at scale, hash-chained file-based approaches or log forwarders with cryptographic signing (like Fluent Bit with TLS + hash verification) are more practical. Rekor excels at storing and verifying specific, high-value entries like deployment signatures, container image digests, and release attestations.

How often should I verify my log integrity chains?

For compliance-driven environments (SOC 2, HIPAA, PCI DSS), automated verification should run at least daily. For high-security environments, real-time verification of each new entry is recommended. The verification process is fast — a Python hash chain verifier can process 100,000 entries in under a second on modern hardware. Store verification results separately from the log files themselves to prevent an attacker from covering their tracks by modifying verification reports.

Do I need a transparency log like Rekor for basic compliance?

Not necessarily. Most compliance frameworks (SOC 2, HIPAA, PCI DSS) accept hash-chained or cryptographically signed log files as sufficient evidence of integrity. Transparency logs like Rekor provide an additional layer of public verifiability and are most valuable for software supply chain security, multi-organization audit scenarios, and environments where you need third-party-verifiable evidence. For single-organization compliance, a well-implemented hash-chained logging system with regular verification is typically sufficient.

Advertise here
Advertise here