Implementing End-to-End TLS/SSL: Don’t Leave Data ‘Exposed’ in Your Internal Network

Security tutorial - IT technology blog
Security tutorial - IT technology blog

The 2 AM Wake-up Call: When External HTTPS is Just a Shell

A phone call at 2 AM usually signals a disaster. A colleague from the Security team sent me a .pcap file captured from a compromised node in our internal network. Opening Wireshark, I froze. Every SQL command, customer info, and session token was clearly visible in plaintext.

My mistake was over-trusting the external HTTPS perimeter. I used Certbot for Nginx and meticulously configured 80-to-443 redirects. However, traffic between the Web Server and the Database was running “naked” over port 3306. To a hacker, an internal network is like a quiet pond. Once they breach a single node, they can easily “scoop up” all your precious data.

Comparing Data Encryption Methods in Transit

To fix my error, I reviewed common approaches before choosing the final solution for our system.

1. SSL Termination (Edge Encryption Only)

This is the model most people use. TLS ends at the Load Balancer or Nginx. Data entering the internal network is pure HTTP or SQL.

  • Pros: Extremely fast setup, reduces server load as backend nodes don’t handle decryption.
  • Cons: Data is completely exposed. If a hacker compromises a container in the same subnet, all external security efforts are rendered useless.

2. SSL Pass-through (Blind Encryption)

Data is encrypted at the client and passes straight to the destination server. The gateway doesn’t interfere with the decryption process.

  • Pros: High security since the Gateway cannot read the content.
  • Cons: You cannot use a WAF (Web Application Firewall) or Layer 7 load balancing (like inspecting headers or cookies).

3. TLS End-to-End (Hop-by-Hop Encryption – The Optimal Choice)

Encrypting the first leg from Client to Nginx, then the second leg from App to Database. This approach provides comprehensive data protection while maintaining the flexibility to control traffic at the Gateway.

The Trade-offs of Implementing End-to-End Encryption

No solution is completely free. Forcing every connection to run over TLS involves trade-offs you should keep in mind:

  • Total Peace of Mind: Even if traffic is sniffed on a switch, hackers only see meaningless blocks of characters.
  • Passing Audits: For financial or medical projects (PCI-DSS, HIPAA), encrypting data in-transit is a “must-have” requirement.
  • Performance Overhead: The handshake adds about 2-5ms of latency per request. However, modern CPUs with AES-NI support handle this smoothly, rarely causing a bottleneck.

Hands-on: Securing the Nginx -> App -> MySQL Connection

Here is the process I followed to seal the loophole that very night.

Step 1: Setting up an Internal CA

Since the database is in a private network, I didn’t use Let’s Encrypt but created my own Certificate Authority (CA) to sign certificates.

# Create Root CA valid for 10 years
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem

# Create Key and Cert for MySQL Server
openssl genrsa -out server-key.pem 2048
openssl req -new -key server-key.pem -out server-req.pem
openssl x509 -req -in server-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem

Step 2: Configuring MySQL Server

Place the certificate files in /etc/mysql/ssl/ and set permissions with chown mysql:mysql. Next, update the my.cnf file:

[mysqld]
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem

# Block all non-SSL connections
require_secure_transport = ON

Then, restart the service: systemctl restart mysql.

Step 3: Connecting from the Application

Now, the Web App needs the ca-cert.pem file to verify the Database’s identity. When creating a new user, I used a Password Generator to get a random 32-character string for safety.

Example configuration with SQLAlchemy in Python:

ssl_params = {
    'ca': '/path/to/ca-cert.pem',
    'check_hostname': True
}

engine = create_engine(
    "mysql+pymysql://user:[email protected]/prod_db",
    connect_args={"ssl": ssl_params}
)

Verifying the Results

All configuration is meaningless without verification. I used the MySQL client from the Web Server to check:

mysql -u user -p -h 10.0.0.5 --ssl-ca=ca-cert.pem

mysql> \s
--------------
SSL: Cipher in use is TLS_AES_256_GCM_SHA384
--------------

The appearance of Cipher in use... means your data is securely wrapped in a TLS cocoon.

Lessons Learned After a Sleepless Night

Implementing internal TLS makes debugging with tcpdump harder. However, the trade-off is a good night’s sleep without fearing audits or lurking hackers. Don’t wait until customer data is being sold on forums to rush into installing SSL for your Database.

Security is not the final destination. It’s the effort to tighten every single loophole every day.

Share: