Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Database Encryption at Rest and In Transit

By Kokil Thapa | Last reviewed: August 2026

If you store personal, financial, or legal data, database encryption at rest and in transit is no longer optional compliance paperwork; it is the baseline defense against disk theft, backup leaks, and network sniffing. I recently audited a production Laravel application where credentials were secure but the database connection string traveled unencrypted over a private VPC, exposing PII to any compromised host on the same segment. This guide covers the exact configuration steps for encrypting data both on disk and over the wire using current 2026 stable releases of MySQL 8.4, PostgreSQL 17, and Laravel 12.x.

What Is the Difference Between Encryption at Rest and In Transit?

Many developers conflate these two protections, but they defend against completely different threat models. Understanding this distinction prevents the common mistake of assuming that enabling SSL on your load balancer satisfies "encryption at rest" requirements. For teams building secure web infrastructure in Nepal, where shared hosting and physical server access remain real risks, separating these concerns is critical.

Encryption At RestDisk / Backup FilesAES-256 / LUKS / TDEThreat: Physical TheftStolen HDD / Leaked S3 BucketEncryption In TransitNetwork ConnectionTLS 1.3 / mTLSThreat: InterceptionMITM / Packet Sniffing
Encryption at rest protects stored data from physical access; encryption in transit protects data moving between application and database.

Encryption at rest protects data when it sits on a hard drive, SSD, or backup tape. If someone steals your server from a Kathmandu data center or gains unauthorized access to an AWS EBS snapshot, this layer ensures the raw bytes are unreadable without the decryption key. Technologies include filesystem encryption (LUKS, ZFS), cloud-managed TDE (AWS RDS, Azure SQL), or database-native TDE (MySQL Enterprise, PostgreSQL with extensions).

Encryption in transit protects data as it moves between your Laravel application and the database server. Without it, any attacker on the network path—whether on a shared switch, a compromised container in the same cluster, or an ISP-level tap—can read queries and results in plaintext. This is enforced via TLS/SSL on the database listener and verified by the client driver.

How Do You Configure TLS for MySQL and PostgreSQL Connections?

In-transit encryption must be mandatory, not opportunistic. The default behavior of many PHP database drivers is to attempt TLS but fall back to plaintext if negotiation fails, which defeats the purpose entirely. Here is how to enforce it strictly.

Enforcing TLS on MySQL 8.4

MySQL 8.4 generates self-signed certificates during initialization, but for production you should issue certificates from your internal CA or Let's Encrypt. Edit your my.cnf:

[mysqld]
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem
require-secure-transport=ON
tls-version=TLSv1.3

The require-secure-transport=ON directive is non-negotiable. It rejects any connection that does not use TLS. On the client side, your Laravel 12 .env must specify verification:

DB_CONNECTION=mysql
DB_HOST=db.example.com
DB_PORT=3306
DB_DATABASE=legal_portal
DB_USERNAME=app_user
DB_PASSWORD="${DB_PASS}"
MYSQL_ATTR_SSL_CA=/etc/app/certs/ca.pem
MYSQL_ATTR_SSL_VERIFY_SERVER_CERT=true

A common mistake I see on client projects is setting MYSQL_ATTR_SSL_CA but omitting VERIFY_SERVER_CERT. Without verification, your app accepts any certificate signed by that CA, including one presented by an attacker performing a man-in-the-middle attack. Always verify.

Enforcing TLS on PostgreSQL 17

PostgreSQL uses pg_hba.conf to control authentication methods. Change all relevant entries from md5 or scram-sha-256 to require SSL:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
hostssl legal_db        app_user        10.0.0.0/24             scram-sha-256
hostssl legal_db        app_user        ::/0                    scram-sha-256

In postgresql.conf, set minimum protocol version:

ssl = on
ssl_cert_file = '/etc/postgresql/17/main/server.crt'
ssl_key_file = '/etc/postgresql/17/main/server.key'
ssl_ca_file = '/etc/postgresql/17/main/root.crt'
ssl_min_protocol_version = 'TLSv1.3'

Laravel's PDO PostgreSQL driver respects the sslmode parameter. Use verify-full for production:

DB_CONNECTION=pgsql
DB_HOST=db.example.com
DB_PORT=5432
DB_DATABASE=legal_db
DB_OPTIONS="sslmode=verify-full;sslrootcert=/etc/app/certs/root.crt"

The verify-full mode checks both the certificate chain and that the hostname matches the CN/SAN. The weaker require mode only checks that TLS was negotiated, leaving you vulnerable to MITM.

Which Encryption-at-Rest Strategy Fits Your Infrastructure?

The right approach depends entirely on who manages your hardware and what compliance regime applies. There is no universal best option, only trade-offs.

MethodBest ForKey ManagementPerformance ImpactComplexity
Cloud TDE (RDS/Azure)Managed databasesProvider-managed KMSNegligibleLow
Filesystem (LUKS/ZFS)Self-hosted VPS/bare metalOS-level keys5–10%Medium
Database-native TDEEnterprise complianceExternal HSM/KMS10–20%High
Application-layerSpecific PII/financial fieldsApp-managed secretsPer-query overheadHigh
Where is DB hosted?Cloud ManagedSelf-Hosted VPSBare MetalEnable Cloud TDE(RDS/Azure Encryption)LUKS + App-Layer(Balanced Security)ZFS/LUKS + TDE(Maximum Protection)Add Column Enc for PIIAdd Column Enc for PIIAdd Column Enc for PII
Decision flow for choosing encryption-at-rest method based on hosting environment and compliance needs.

For most Laravel projects I deploy on Ubuntu 24.04 servers, filesystem-level encryption via LUKS provides the best balance. It requires no application changes, works with any database engine, and protects backups automatically if they reside on the same encrypted volume. Cloud TDE is superior when available because key rotation and audit logging are handled by the provider, eliminating operational risk.

When Should You Add Application-Layer Column Encryption?

Infrastructure encryption protects against external threats, but insiders with legitimate database access can still read plaintext data. For legal-tech portals handling citizenship numbers, marriage certificates, or court documents, application-layer encryption ensures that even a DBA with root access cannot view sensitive fields without the application's decryption key. This is the principle of separation of duties.

Laravel 12 includes native encrypted casting via Eloquent. Define the attribute in your model:

class Client extends Model
{
    protected $casts = [
        'citizenship_number' => 'encrypted',
        'court_case_details' => 'encrypted',
    ];
}

This uses AES-256-CBC with the APP_KEY by default. However, tying encryption to APP_KEY creates a single point of failure: rotating the app key breaks all existing encrypted data. For production systems, implement a dedicated encryption service with per-field keys stored in HashiCorp Vault or AWS Secrets Manager:

use Illuminate\Support\Facades\Crypt;
use App\Services\FieldEncryptionService;

class Client extends Model
{
    public function getCitizenshipNumberAttribute($value): ?string
    {
        return $value ? FieldEncryptionService::decrypt('citizenship', $value) : null;
    }

    public function setCitizenshipNumberAttribute(?string $value): void
    {
        $this->attributes['citizenship_number'] = $value 
            ? FieldEncryptionService::encrypt('citizenship', $value) 
            : null;
    }
}

Critical caveat: encrypted columns cannot be indexed or searched efficiently. If you need to query by citizenship number, store a blinded hash (HMAC-SHA256 with a separate key) alongside the ciphertext. Query the hash, decrypt the result. This pattern appears frequently in Nepal legal-tech implementations where searchability and confidentiality must coexist.

How Do You Manage Keys and Rotate Them Safely?

Encryption is only as strong as your key management. Losing keys means permanent data loss; leaking keys means total compromise. Treat key lifecycle as a first-class engineering concern.

  1. Never store keys in code or .env files on disk. Inject them at runtime via environment variables from a secrets manager. On Deployer 7 pipelines I maintain, secrets are pulled from GitLab CI variables and injected into the release .env during deployment, never committed to Git.
  2. Separate data encryption keys (DEKs) from key encryption keys (KEKs). DEKs encrypt actual data; KEKs encrypt DEKs. Rotating a KEK re-wraps DEKs without re-encrypting terabytes of data.
  3. Automate rotation. Manual rotation fails. Schedule quarterly KEK rotation via cron or CI pipeline. Test restoration from backup after every rotation cycle.
  4. Audit key access. Log every decrypt operation. Alert on anomalous patterns (bulk decryption outside business hours, access from unexpected IPs).
  5. Document recovery procedures. When a server dies at 2 AM, nobody remembers where the KEK backup lives. Maintain runbooks tested quarterly.
Master Key (KEK)Stored in Vault / HSMDEK: Users TableWrapped by KEKDEK: DocumentsWrapped by KEKDEK: PaymentsWrapped by KEKEncrypted RecordsAES-256-GCM CiphertextEncrypted FilesSealed Document BlobsRotation: Re-wrap DEKs OnlyNo Data Re-encryption Needed
Hierarchical key management: master KEK wraps individual DEKs, enabling safe rotation without re-encrypting all data.

On projects using Laravel for sensitive applications, I typically configure HashiCorp Vault as the KEK store. The application authenticates via AppRole, retrieves DEKs at boot, caches them in memory (never on disk), and discards them on shutdown. This adds complexity but eliminates the catastrophic scenario where a leaked .env file exposes years of client data.

Verifying Your Encryption Setup Works End-to-End

Configuration alone is insufficient. You must verify that encryption is actually active and cannot be silently downgraded. Run these checks in CI and post-deployment:

  • TLS verification: Connect with mysql --ssl-mode=VERIFY_IDENTITY or psql "sslmode=verify-full". If connection succeeds, TLS is enforced. Attempt connection with --ssl-mode=DISABLED; it must fail.
  • Certificate expiry monitoring: Set up Prometheus or UptimeRobot alerts for certificate expiration 30 days out. Expired certs cause silent fallback or hard outages.
  • At-rest verification: For LUKS, confirm lsblk -o NAME,FSTYPE,TYPE shows crypto_LUKS. For cloud TDE, check the provider console encryption status flag.
  • Column encryption test: Insert known plaintext, query directly via mysql CLI, confirm ciphertext is returned. Decrypt via application, confirm match.
  • Backup encryption: Restore backup to isolated environment, verify encryption persists. Unencrypted backups negate all at-rest protections.

I add these checks to every GitLab CI pipeline for legal-tech and e-commerce clients. A passing test suite that doesn't validate encryption gives false confidence. Make verification automatic and blocking.

Implementing Database Encryption at Rest and In Transit Today

Start with in-transit TLS enforcement—it takes under an hour and closes the highest-risk gap immediately. Then assess your at-rest posture based on hosting environment. Add application-layer encryption only for fields where regulatory or contractual obligations demand it. Document your key management procedures before you need them at 3 AM. If you're building systems that handle sensitive Nepali citizen data or international client information and want a second pair of eyes on your encryption architecture, reach out to discuss your specific requirements.

Frequently Asked Questions

Encryption at rest protects stored data files on disk using algorithms like AES-256, preventing access if physical media is stolen. Encryption in transit secures data moving between client and server via TLS/SSL, stopping network interception. Both are required for complete protection; one without the other leaves a critical vulnerability gap in your infrastructure.

Yes, expect 3-5% overhead for TLS handshakes and 1-3% for disk I/O with modern NVMe storage. On production Laravel applications I have managed, enabling MySQL TDE and enforcing TLS added negligible latency because hardware acceleration handles AES-NI instructions. The real bottleneck is usually unoptimized queries or missing indexes, not the encryption layer itself.

Configure the sslmode option in config/database.php to require or verify-ca. Ensure your MySQL server has valid certificates and the mysql user requires SSL. In my experience deploying legal-tech portals, skipping certificate verification exposes you to man-in-the-middle attacks. Always bundle the CA cert in your deployment artifacts and validate the full chain during GitLab CI pipeline tests.

No, TDE is exclusive to MySQL Enterprise Edition. Community users must rely on filesystem-level encryption like LUKS or ZFS native encryption. For budget-sensitive Nepal projects costing under Rs 500,000, I consistently recommend LUKS on Ubuntu 24 servers over expensive Enterprise licenses. It provides equivalent at-rest protection for stolen drives without recurring licensing fees.

Open-source tools like Let's Encrypt and LUKS cost zero in licensing, only engineering time. Expect Rs 30,000–60,000 (USD 225–450) for initial setup and testing by a senior developer. Enterprise TDE licenses start around USD 5,000 annually. For most Nepali SMEs, the open-source stack provides sufficient compliance and security without the enterprise price tag.

Encrypted backups require secure key management separate from the backup storage. If you lose the encryption key, the backup is permanently unrecoverable. On production systems I maintain, we store keys in HashiCorp Vault or AWS KMS, never on the same server as the database. Test restores quarterly to verify key accessibility and decryption integrity under real recovery conditions.

Yes, application-level column encryption using Laravel's Crypt facade or MySQL's AES_ENCRYPT function targets sensitive fields like PAN numbers or passwords. This avoids full-disk overhead but complicates searching and indexing encrypted columns. I use this pattern for legal document metadata where only specific case identifiers need protection while keeping general case data queryable for admin dashboards.

Encryption at rest and in transit satisfies PCI DSS requirement 3.4 and NRB IT guidelines for financial data protection. However, compliance also demands key rotation policies, access logging, and documented procedures. Merely enabling encryption without governance documentation will fail an audit. I have helped fintech clients pass NRB inspections by combining technical controls with proper operational documentation.

Use envelope encryption where data keys encrypt data and master keys encrypt data keys. Rotate master keys periodically without re-encrypting all data. For MySQL Enterprise, use the keyring plugin with dual-key support. On community editions with LUKS, add a new key slot then remove the old one. Always test rotation in staging first; I have seen production outages from untested key rotations.

All encrypted data becomes permanently inaccessible. There is no backdoor or recovery mechanism by design. Store keys in redundant, geographically separated secure locations with strict access controls. On client projects, I implement automated key backup to encrypted S3 buckets with versioning. Treat key loss prevention with the same urgency as database backup verification.

Use TLS 1.3 exclusively. SSL 2.0/3.0 and TLS 1.0/1.1 are deprecated and vulnerable. MySQL 8.4 defaults to TLS 1.3 when both sides support it. Configure your server to reject older protocols explicitly. Legacy PHP 8.2 clients may need OpenSSL 3.x updates. Never negotiate down to insecure versions for backward compatibility; upgrade the client instead.

Replication traffic between primary and replica nodes must also be encrypted via TLS. Unencrypted replication streams expose data even if storage is encrypted. Configure source and replica with matching TLS certificates and enforce secure transport. In Galera or Group Replication clusters, encrypt the group communication channel separately. I verify replication encryption status monthly using SHOW REPLICA STATUS on production systems.

Full-text search and range queries on encrypted columns are impossible without decryption. Use deterministic encryption for equality searches or maintain separate hashed indexes for lookups. For complex search needs, consider searchable encryption schemes or keep non-sensitive metadata unencrypted. On e-commerce platforms like Nepal Gift Card, we encrypt payment tokens but leave product SKUs and order dates queryable for operational reporting.

Storing keys alongside encrypted data, skipping TLS certificate validation, forgetting to encrypt backups, and neglecting key rotation testing. Another frequent error is assuming encryption replaces access controls; compromised credentials bypass encryption entirely. Implement defense in depth: encryption plus RBAC plus audit logging. Review configurations against CIS benchmarks before going live.

For transit, run tcpdump or Wireshark to confirm no plaintext SQL appears on the wire. For at-rest, inspect raw data files with hexdump to verify ciphertext. Check MySQL variables SHOW VARIABLES LIKE '%ssl%' and encryption status commands. Automate these checks in your CI pipeline. On deployments via Deployer 7, I include post-deploy verification scripts that fail the release if encryption is misconfigured.

Share this article

Quick Contact Options
Choose how you want to connect me: