Practical Application Security for Developers - Part 3: TLS and Secure Communication
A practical guide to TLS 1.3, mTLS, ingress termination, Spring Boot HTTPS, and production hardening.
Practical Application Security for Developers - Part 3: TLS and Secure Communication
In Part 1 we covered symmetric cryptography, and in Part 2 we covered asymmetric cryptography and certificates. This part brings those building blocks together in the place most developers actually meet them: TLS.
You do not implement TLS from scratch. You choose where it terminates, which versions and ciphers are allowed, and how certificates are issued, renewed, and verified.
Why TLS Matters
Every request your application sends crosses infrastructure you do not fully control. TLS gives you three guarantees even on an untrusted network:
| Threat | TLS protection | Mechanism |
|---|---|---|
| Eavesdropping | Confidentiality | Symmetric encryption after the handshake |
| Tampering | Integrity | Authenticated record protection |
| Impersonation | Authentication | Certificate-based server identity |
TLS versus SSL
| Protocol | Status |
|---|---|
| SSL 2.0 | Broken |
| SSL 3.0 | Broken |
| TLS 1.0 | Deprecated |
| TLS 1.1 | Deprecated |
| TLS 1.2 | Acceptable minimum |
| TLS 1.3 | Preferred |
Use TLS 1.2 or TLS 1.3. Disable everything older.
How a TLS 1.3 Handshake Works
A simplified TLS 1.3 handshake looks like this:
Why TLS 1.3 Is Better
- Fewer legacy options to misconfigure
- Faster handshakes than TLS 1.2
- Better defaults for modern cipher suites
- Cleaner path to strong forward secrecy
One-Way TLS versus mTLS
| Aspect | Standard TLS | Mutual TLS |
|---|---|---|
| Server proves identity | Yes | Yes |
| Client proves identity | No | Yes |
| Common use case | Browser to web server | Service to service |
mTLS becomes useful when internal services must prove who they are without relying on network location alone.
TLS Termination Choices
Where TLS ends matters operationally and architecturally.
| Strategy | Terminates at | Best fit |
|---|---|---|
| Edge termination | Load balancer or CDN | Simple public websites |
| Ingress termination | Kubernetes ingress controller | Common Kubernetes deployments |
| Re-encryption | Ingress and then backend HTTPS | Compliance-heavy environments |
| End-to-end passthrough | Application itself | Sensitive internal tooling |
| Service mesh mTLS | Sidecars or mesh proxies | Zero-trust service environments |
For many teams, a sensible default is:
- Ingress termination for north-south traffic
- mTLS via service mesh for east-west traffic
Kubernetes TLS with cert-manager and Ingress
A common production setup is cert-manager plus an ingress controller.
ClusterIssuer Example
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: devops@yourcompany.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginxIngress Example
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.yourcompany.com
secretName: api-yourcompany-tls
rules:
- host: api.yourcompany.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080cert-manager handles issuance and renewal. The ingress controller serves the certificate.
Spring Boot HTTPS
Sometimes the application itself must terminate TLS.
application.properties
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=${SSL_KEYSTORE_PASSWORD}
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=myapp
server.ssl.enabled-protocols=TLSv1.3,TLSv1.2Redirect HTTP to HTTPS
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HttpsRedirectConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addAdditionalTomcatConnectors(httpConnector());
return factory;
}
private Connector httpConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}Enable Client Certificates for mTLS
server.ssl.client-auth=need
server.ssl.trust-store=classpath:truststore.p12
server.ssl.trust-store-password=${SSL_TRUSTSTORE_PASSWORD}
server.ssl.trust-store-type=PKCS12Local Development TLS
Running HTTPS locally helps you catch real cookie, CORS, and redirect behavior early.
openssl pkcs12 -export \
-in server_cert.pem \
-inkey server_key.pem \
-CAfile ca/ca_cert.pem \
-out src/main/resources/keystore.p12 \
-name localhost \
-passout pass:devonlyThen point Spring Boot at that keystore in a development profile.
Useful Debugging Commands
# Test TLS 1.3 negotiation
openssl s_client -connect api.yourcompany.com:443 -tls1_3
# Check remote certificate dates
echo | openssl s_client -connect api.yourcompany.com:443 2>/dev/null \
| openssl x509 -noout -dates
# Show the certificate chain
openssl s_client -connect api.yourcompany.com:443 -showcertsFor JVM troubleshooting, -Djavax.net.debug=ssl:handshake is still one of the most useful flags you can enable.
Security Hardening Checklist
- Enforce TLS 1.2 minimum, prefer TLS 1.3
- Redirect HTTP to HTTPS
- Use modern cipher suites
- Automate certificate renewal
- Monitor certificate expiry
- Protect private keys and keystore passwords
- Use HSTS where appropriate
- Avoid disabling certificate verification in production
Common Mistakes to Avoid
- Accepting legacy TLS versions for convenience
- Leaving HTTP reachable without redirect
- Treating self-signed production certs as acceptable
- Forgetting certificate renewal automation
- Terminating TLS in too many layers without a reason
- Ignoring trust-store problems until production traffic fails
Part 3 Wrap-Up
At this point, the cryptographic pieces from the first three parts should fit together clearly:
- Part 1 explained symmetric primitives
- Part 2 explained asymmetric keys and certificates
- Part 3 showed how TLS uses them in real systems
What's Next
In Part 4, we move up the stack from transport security to token-based identity: JOSE, JWS, JWE, JWK, JWKS, and what secure token validation actually requires.
Continue Learning
Explore more guides and resources to deepen your knowledge.