
August 15, 2026
9 min read
Table of Contents
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 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.
| Method | Best For | Key Management | Performance Impact | Complexity |
|---|---|---|---|---|
| Cloud TDE (RDS/Azure) | Managed databases | Provider-managed KMS | Negligible | Low |
| Filesystem (LUKS/ZFS) | Self-hosted VPS/bare metal | OS-level keys | 5–10% | Medium |
| Database-native TDE | Enterprise compliance | External HSM/KMS | 10–20% | High |
| Application-layer | Specific PII/financial fields | App-managed secrets | Per-query overhead | High |
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.
- 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
.envduring deployment, never committed to Git. - 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.
- Automate rotation. Manual rotation fails. Schedule quarterly KEK rotation via cron or CI pipeline. Test restoration from backup after every rotation cycle.
- Audit key access. Log every decrypt operation. Alert on anomalous patterns (bulk decryption outside business hours, access from unexpected IPs).
- Document recovery procedures. When a server dies at 2 AM, nobody remembers where the KEK backup lives. Maintain runbooks tested quarterly.
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_IDENTITYorpsql "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,TYPEshows crypto_LUKS. For cloud TDE, check the provider console encryption status flag. - Column encryption test: Insert known plaintext, query directly via
mysqlCLI, 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.

