Spring Boot Servlet Containers: Port, SSL/TLS, Graceful Shutdown
Master Spring Boot servlet container configuration: custom ports, SSL/TLS setup, and graceful shutdown for production-ready applications.
Master Spring Boot servlet container configuration: custom ports, SSL/TLS setup, and graceful shutdown for production-ready applications. The guide uses practical examples to explain understanding embedded servlet containers, customizing the server port and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.
Spring Boot Servlet Containers: Port, SSL/TLS, Graceful Shutdown
Spring Boot ships with embedded servlet containers, which is convenient until you need to debug a port conflict at 2am or watch requests get dropped during a rolling deployment. This guide walks through port configuration, SSL/TLS setup, and graceful shutdown—the three container settings that matter most in production.
Understanding Embedded Servlet Containers
Introduction
An embedded servlet container gives a Spring Boot web application its HTTP server without requiring a separately installed application server. This guide explains how embedded Tomcat, Jetty, and Undertow receive requests, how to configure ports and server behavior, and how container choices affect deployment and runtime operations.
Customizing the Server Port
Default Behavior
Out of the box, Spring Boot listens on port 8080. The server.port property controls this, with SERVER_PORT as an environment variable fallback.
Configuration Options
# Change the port
server.port=8080
# Bind to a specific interface
server.address=0.0.0.0
# Pick a random available port (handy for tests)
server.port=0
server:
port: 8443
address: 127.0.0.1
When to Use Custom Ports
Custom ports make sense when:
- You are running multiple microservices on the same host
- Your deployment platform assigns ports dynamically (Cloud Foundry, Heroku)
- You need to isolate internal services from external-facing ones
- You want to avoid port conflicts in local development
When to Skip Custom Ports
Don’t overconfigure ports if:
- Your organization enforces a standard port layout
- Your platform handles port routing automatically
- You are just spinning up a quick demo
Servlet Container Lifecycle
Here is how the container moves through its phases:
graph TD
A[Application Starts] --> B[Embedded Container Created]
B --> C[Network Socket Binds to Port]
C --> D[HTTP Connector Initialized]
D --> E[Application Ready - Requests Accepted]
E --> F[Shutdown Signal Received]
F --> G[Graceful Shutdown Phase]
G --> H[In-Flight Requests Complete]
H --> I[Container Stops - Socket Closed]
I --> J[Application Context Closed]
The key phase is “Graceful Shutdown Phase” – new connections stop, but existing requests get to finish.
Configuring SSL/TLS
Why SSL Matters
Even services behind a load balancer can have traffic intercepted. TLS encrypts what crosses the network, and these days there is really no excuse to skip it for any service that handles meaningful data.
If you need a refresher on the TLS protocol stack, see our SSL/TLS and HTTPS guide.
Generating a Keystore
You need a Java KeyStore (JKS) holding your certificate and private key:
# Create a new keystore with a key pair
keytool -genkeypair -alias springboot -keyalg RSA \
-keysize 2048 -keystore keystore.jks \
-validity 365 -storepass changeit -keypass changeit
# Export the certificate if you need external systems to trust it
keytool -exportcert -alias springboot -file certificate.cer \
-keystore keystore.jks -storepass changeit
For anything beyond local development, get a certificate from a real CA. Let’s Encrypt works well for public services. Internal PKI or your company’s security team is the right path for enterprise internal services.
SSL Configuration in Spring Boot
# Basic SSL setup
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=changeit
server.ssl.key-password=changeit
server.ssl.key-alias=springboot
# Tighten it down
server.ssl.protocol=TLS
server.ssl.enabled-protocols=TLSv1.3,TLSv1.2
server.ssl.ciphers=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256
server:
port: 8443
ssl:
enabled: true
key-store: "classpath:keystore.jks"
key-store-password: "changeit"
key-password: "changeit"
key-alias: "springboot"
protocol: "TLS"
enabled-protocols:
- "TLSv1.3"
- "TLSv1.2"
ciphers:
- "TLS_AES_256_GCM_SHA384"
- "TLS_AES_128_GCM_SHA256"
Certificate Types
| Type | When to use it | Good | Bad |
|---|---|---|---|
| Self-signed | Local dev only | Instant | Not trusted anywhere |
| CA-issued | Production | Trusted universally | Costs money, setup delay |
| Let’s Encrypt | Public services | Free, auto-renewal | Needs domain validation |
| Internal PKI | Enterprise internal | Fits your org | Requires running a PKI |
When to Enable SSL
Turn it on when:
- The service is reachable from outside your network
- Compliance matters (PCI-DSS, HIPAA, SOC2, whatever applies to your situation)
- Services talk to each other across machines
- Your security team requires it
You can probably skip it when:
- Everything is
localhostonly - A service mesh handles mTLS for you
- You are in early prototyping and performance tracing is the priority
Graceful Shutdown
Without graceful shutdown, a SIGTERM kills requests mid-flight. Connections drop, transactions roll back, users get errors. Graceful shutdown lets in-flight requests finish before the process exits.
Enabling It
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
server:
shutdown: "graceful"
spring:
lifecycle:
timeout-per-shutdown-phase: "30s"
How It Works
sequenceDiagram
participant K8s as Kubernetes
participant App as Spring Boot App
participant LB as Load Balancer
K8s->>App: SIGTERM Signal
Note over App: Shutdown begins
App->>LB: Health check failure
Note over LB: Removed from rotation
LB->>App: Existing requests continue
loop In-Flight Requests
App->>App: Processing...
end
App-->>K8s: Shutdown complete
Note over App: Process exits cleanly
The health endpoint starts returning failures, the load balancer drains the instance, existing requests complete, then the process stops.
Setting the Timeout
Default is 30 seconds. Size it to your actual request patterns:
- What is your p99 latency?
- Do you have long-running requests (file uploads, report generation)?
- What does your Kubernetes
terminationGracePeriodSecondsallow?
# If you know you have longer-running requests
spring.lifecycle.timeout-per-shutdown-phase=60s
When It Matters
Graceful shutdown is worth the configuration when:
- Requests touch databases or message queues
- You have external API dependencies
- You are running in Kubernetes
- Dropped connections would leave data in an inconsistent state
When You Can Skip It
It is safe to disable graceful shutdown when:
- Requests are stateless and finish in milliseconds
- Blue-green deployments route traffic before the old instance stops
- It is a dev or test environment
- A dropped request has no side effects
Implementation Checklist
Port Configuration
- Pick the right port for each environment
- Keep a record of any non-standard ports
- Check firewall rules before deploying
- Make sure health endpoints are reachable on the right port
SSL/TLS Setup
- Get certificates from a CA (not self-signed for production)
- Keep the keystore out of version control
- Use strong passwords (and store them in a secrets manager, not a properties file)
- Lock down protocols to TLS 1.2 and up
- Set up HSTS headers
- Redirect HTTP to HTTPS
Graceful Shutdown Checklist
- Set
server.shutdown=graceful - Triage your request duration and set the timeout accordingly
- Align with your Kubernetes
terminationGracePeriodSeconds - Actually test shutdown under load before going to production
- Keep an eye on shutdown duration in production
When to Use / When NOT to Use
Custom Ports Use Cases
Custom ports make sense when you are running multiple microservices on the same host, your deployment platform assigns ports dynamically (Cloud Foundry, Heroku), you need to isolate internal services from external-facing ones, or you want to avoid port conflicts in local development.
When NOT to Use Custom Ports
Do not add custom port configuration if your organization enforces a standard port layout, your platform handles port routing automatically, or you are just spinning up a quick demo. Unnecessary port configuration creates operational overhead and becomes yet another thing to document and maintain.
When to Use SSL/TLS
Turn on SSL when the service is reachable from outside your network, compliance matters (PCI-DSS, HIPAA, SOC2), services talk to each other across machines, or your security team requires it.
When to Skip SSL/TLS
You can skip SSL when everything is localhost-only, a service mesh handles mTLS for you, or you are in early prototyping where performance tracing is the priority.
When to Enable Graceful Shutdown
Graceful shutdown is worth the configuration when requests touch databases or message queues, you have external API dependencies, you are running in Kubernetes, or dropped connections would leave data in an inconsistent state.
When to Disable Graceful Shutdown
It is safe to disable when requests are stateless and finish in milliseconds, blue-green deployments route traffic before the old instance stops, it is a dev or test environment, or a dropped request has no side effects.
Production Failure Scenarios
Port Conflict at Startup
Symptom: Application fails to start with “Port 8080 already in use.”
Cause: Another process is bound to the same port. This happens after failed deployments that did not terminate cleanly, or when running multiple instances locally without accounting for port offsets.
Fix: Run netstat -tlnp | grep <port> to find the conflicting process. Either stop it or change your port configuration. In Kubernetes, use a Service to map from a stable external port to your container port rather than hardcoding port numbers.
SSL Handshake Failures in Production
Symptom: Clients report “SSL handshake failed” or “certificate chain incomplete” errors.
Cause: The certificate has expired, the intermediate CA certificate is missing from the keystore, or the client does not trust the signing CA.
Fix: Check certificate expiration with keytool -list -v -keystore keystore.jks. For production, automate renewal using cert-manager with Let’s Encrypt for public services, or your internal PKI for private services. Always include the full certificate chain in the keystore.
Graceful Shutdown Timeout Too Short
Symptom: Deployments cause intermittent connection resets and 503 errors even with graceful shutdown enabled.
Cause: spring.lifecycle.timeout-per-shutdown-phase is shorter than your actual request processing time. When the timeout fires, in-flight requests are killed mid-execution.
Fix: Profile your actual request durations under load. Set the shutdown timeout to at least 2x your p99 latency. If you have long-running requests (file uploads, batch jobs), size the timeout accordingly and coordinate with your Kubernetes terminationGracePeriodSeconds.
Secrets Leaked Through Properties Files
Symptom: Keystore passwords appear in logs, error reports, and git history.
Cause: Passwords were stored in application.properties or application.yml which is committed to version control.
Fix: Pull all secrets from environment variables or a secrets manager. Spring Boot reads server.ssl.key-store-password from ${KEYSTORE_PASSWORD} natively. Use Kubernetes Secrets, HashiCorp Vault, or AWS Secrets Manager for production secret management.
Trade-Off Table
| Configuration | Port Customization | SSL/TLS Setup | Graceful Shutdown |
|---|---|---|---|
| Complexity | Low | Medium (keystore management) | Low |
| Risk if misconfigured | Deployment failure | Service unreachable or insecure | Connection drops on deploy |
| Automation available | No | cert-manager (Let’s Encrypt) | No |
| Required for compliance | No | Often yes (PCI, HIPAA, SOC2) | Often yes |
| Performance impact | None | Encryption overhead (~2-3%) | None |
| Debugging difficulty | Easy (port conflict) | Medium (chain issues) | Medium (timeout tuning) |
Observability Checklist
- Confirm
server.portis explicitly set for each environment (do not rely on defaults in production) - Verify SSL certificate expiration dates are tracked in a monitoring system
- Test graceful shutdown under load before going to production — do not wait for a real deployment to discover the timeout is too short
- Align
spring.lifecycle.timeout-per-shutdown-phasewithterminationGracePeriodSecondsin your Kubernetes deployment - Monitor shutdown duration in production — if shutdown regularly approaches the timeout, requests are being killed
- Log the SSL configuration at startup (certificate subject, issuer, validity dates) to aid debugging
- Ensure keystore path is read from an environment variable, not hardcoded in properties
- Verify health endpoints are accessible on the configured port during shutdown
Common Pitfalls / Anti-Patterns
Port Already in Use
Symptom: Application will not start. “Port already in use.”
Fix: netstat -tlnp | grep <port> to find the culprit. Either stop that process or pick a different port.
Certificate Expiration
Symptom: SSL handshake starts failing on a specific date.
Fix: Monitor expiration proactively. keytool -list shows you remaining validity. Automate renewal for production certs.
Shutdown Timeout Too Short
Symptom: Deployments cause intermittent connection resets.
Fix: Bump spring.lifecycle.timeout-per-shutdown-phase. If requests regularly take longer than your timeout, that is a separate problem worth investigating.
Passwords in Plain Text Properties
Symptom: Keystore passwords show up in logs, git history, and error reports.
Fix: Pull secrets from environment variables or a vault. Spring Boot can read server.ssl.key-store-password from ${KEYSTORE_PASSWORD}.
Security Notes
- Keep keystores out of version control – Add
*.jksto.gitignoreand use a secrets manager. - Rotate certificates before they expire – Automated renewal avoids urgent renewals at midnight.
- Drop deprecated protocols – SSLv3, TLS 1.0, and TLS 1.1 have known vulnerabilities. Lock to TLS 1.2+.
- Use strong ciphers only – Your SSL config should not include anything from the 1990s.
- Consider mTLS for service-to-service – When containers talk to each other, mutual TLS verifies both sides.
Quick Recap Checklist
- Spring Boot includes embedded containers (Tomcat by default)
- Set the port with
server.port - Turn on SSL with
server.ssl.enabled=trueand point to your keystore - Enable graceful shutdown with
server.shutdown=graceful - Match
spring.lifecycle.timeout-per-shutdown-phaseto your request patterns - Align the application shutdown timeout with your Kubernetes
terminationGracePeriodSeconds - Keep keystores and passwords out of source control
Interview Questions
Set server.port in application.properties or application.yml. For example, server.port=8443. The SERVER_PORT environment variable also works. One trick for tests: set server.port=0 and Spring Boot picks a random available port.
Graceful shutdown prevents requests from dying when the application receives a termination signal. Instead of killing connections immediately, the server stops accepting new requests, lets existing ones finish, runs @PreDestroy callbacks, and then exits cleanly. Without it, in-flight requests fail with connection resets, which can leave database transactions half-committed or external webhooks unanswered.
You need a Java KeyStore holding your certificate and private key. Then set these properties: server.ssl.enabled=true, server.ssl.key-store (the JKS path), server.ssl.key-store-password, server.ssl.key-password, and optionally server.ssl.key-alias. Remember to change the port too—HTTPS typically runs on 8443 in development and 443 in production.
When a SIGTERM arrives and graceful shutdown is enabled: the server stops accepting new connections, health endpoints begin failing to pull the instance out of load balancer pools, in-flight requests keep processing until they complete or the timeout fires, the Spring application context closes triggering @PreDestroy lifecycles, and finally the JVM exits with code 0. The spring.lifecycle.timeout-per-shutdown-phase caps how long the whole thing waits.
Store the keystore as a Kubernetes Secret or pull it from an external secrets manager like Vault or AWS Secrets Manager. Mount it as a volume and reference the path via environment variable: server.ssl.key-store=${KEYSTORE_PATH}. For automatic certificate management on public services, cert-manager with Let's Encrypt is the standard path. If you need mTLS between services inside the cluster, a service mesh like Istio handles certificate rotation and verification automatically.
Tomcat is the default—battle-tested and Java EE compatible. Jetty is lighter weight with faster startup, making it good for microservice contexts where container image size matters. Undertow is non-blocking and reactive-native, a better fit for non-blocking I/O workloads. All three expose the same Spring Boot configuration surface for port, SSL, and shutdown—you switch between them purely by changing dependencies.
It happens inside SpringApplication.run(). The WebServerApplicationContext creates the web server factory (a TomcatServletWebServerFactory by default), which calls getWebServer() to create the embedded container. The container binds to the configured port, initializes the HTTP connector, and starts accepting requests. The whole chain is pluggable—replacing the factory changes which container gets created.
server.port=0 in a test context?It tells Spring Boot to pick any available port. This avoids flakiness when tests run in parallel on a shared machine where a fixed port might already be bound. The actual port gets injected into tests via @LocalServerPort or the Environment, so test code does not need to hardcode a port number.
server.address and server.port in Spring Boot configuration?server.port controls which TCP port the container binds to. server.address controls which network interface IP address it binds to. By default the container binds to all interfaces (0.0.0.0). Setting server.address=127.0.0.1 restricts it to localhost only, which is useful for internal-only services you do not want exposed externally even within a private network.
Another process on the host has claimed that port. Run netstat -tlnp | grep 8080 (Linux) or netstat -ano | findstr 8080 (Windows) to find the PID. Check whether it is a previous failed Spring Boot instance, another service on the same host, or something running in a sibling container. In Kubernetes, verify your Service is not inadvertently mapping a host port directly to the container port.
Set server.http-port=8080 to keep an HTTP connector active alongside the HTTPS connector on 8443. Then add a TomcatContextCustomizer or a WebServerFactoryCustomizer that calls connector.setRedirectPort(8443) so that requests hitting port 8080 get a 301 redirect to the HTTPS port. Alternatively, handle this at the load balancer or ingress layer rather than in the application.
Port 8080 is widely recognized as a non-production debug port. Misconfigured network policies, security scans, or automated exploitation tools may target it. Using a non-standard port like 8443 for HTTPS or configuring the service behind a proper Ingress with a defined external port reduces accidental exposure. More importantly, production services should explicitly configure their ports rather than relying on defaults—this prevents port confusion when deploying to environments with different conventions.
spring.lifecycle.timeout-per-shutdown-phase is set shorter than the time needed to process in-flight requests?When the timeout fires, the container kills in-flight requests mid-execution. Clients receive a connection reset or 503 error. Any database transaction in progress rolls back, message queue operations leave pending messages unacknowledged, and external webhooks go unanswered. This creates data inconsistency and makes debugging difficult because the shutdown looks clean from the outside while requests are silently failing. Always set the timeout to at least 2x your p99 request duration.
Once the shutdown phase begins, the readiness probe starts failing and the kubelet stops routing new traffic to the pod. The liveness probe is irrelevant during shutdown since the process is already terminating. If you expose a dedicated /shutdown actuator endpoint, it should fail health checks to ensure it is not used for routing. The pod stays in a terminating state until all requests drain and the timeout expires, then the kubelet sends a SIGKILL if the process has not exited.
Any password in a properties file ends up in git history, build logs, error reports, and potentially leaked through secrets sprawl. Use environment variable substitution: server.ssl.key-store-password=${KEYSTORE_PASSWORD}. For Kubernetes, store the password in a Secret and inject it as an environment variable. For enterprise environments, HashiCorp Vault or AWS Secrets Manager provide audit logs and automatic rotation. Never commit keystore files to version control either—add *.jks to .gitignore.
TLS handshake adds latency on connection establishment—roughly 2-3 round trips before the first request can be sent. Modern TLS 1.3 reduces this to 1-RTT with 0-RTT resumption. CPU overhead for symmetric encryption is modest on modern hardware (typically 2-3% for high-throughput services). The bigger cost is memory for SSL session caches. Enable HTTP/2 to amortize handshake costs across multiplexed requests on a single connection.
server.ssl.enabled-protocols and which ones should you exclude?It restricts which TLS protocol versions the container will accept. You should explicitly exclude SSLv3, TLS 1.0, and TLS 1.1 because they have known vulnerabilities (POODLE, BEAST, etc.). Lock to TLSv1.3,TLSv1.2 only. TLS 1.3 is preferred because it supports 0-RTT resumption and removes obsolete cipher suites. If you must support legacy clients that only speak TLS 1.2, include it but monitor for clients still attempting TLS 1.0/1.1.
server.shutdown=graceful behave differently from the default immediate shutdown?With immediate shutdown (the default), the container stops accepting connections and calls Context.close() right away—active requests get a connection reset. With server.shutdown=graceful, the container stops accepting new connections but waits for existing requests to complete up to the configured timeout. The Spring application context also closes gracefully, triggering @PreDestroy hooks. The tradeoff is that a misconfigured timeout or runaway requests can block the shutdown indefinitely.
spring.lifecycle.timeout-per-shutdown-phase and Kubernetes terminationGracePeriodSeconds?Kubernetes sends a SIGTERM and waits up to terminationGracePeriodSeconds before sending SIGKILL. Spring Boot's shutdown timeout should be strictly shorter than terminationGracePeriodSeconds to allow time for the kubelet to notice the process has exited. A typical setup: terminationGracePeriodSeconds=45 and spring.lifecycle.timeout-per-shutdown-phase=30s. If Spring Boot shuts down in 30 seconds, the remaining 15 seconds gives the kubelet time to clean up before the hard kill.
Undertow's non-blocking I/O model makes it a better fit for reactive Spring WebFlux applications or services handling long-lived connections (WebSockets, Server-Sent Events). It also has a smaller memory footprint than Tomcat, which matters in dense container environments. However, Undertow has less battle-tested integration with some Java EE specs and fewer operational tools. Use it when you need the performance characteristics of non-blocking I/O; stick with Tomcat for standard blocking web applications where operational simplicity wins.
Further Reading
- Embedded Web Servers: Tomcat, Jetty, Undertow — Compare servlet container options beyond the defaults
- SSL/TLS and HTTPS — Protocol stack deep dive and HTTPS setup
- mTLS (Mutual TLS) — Service-to-service certificate authentication
- Spring Boot Actuator Deep Dive — Production-ready endpoints including health and shutdown
- Spring Boot Configuration Properties — Externalized configuration patterns
- Spring Boot Profiles and Environments — Environment-specific container configuration
- Spring Boot Testing — Testing servlet container behavior including SSL and shutdown
Conclusion
Spring Boot’s embedded servlet containers expose three production-critical configuration axes: the port you bind to, the TLS setup protecting your traffic, and the graceful shutdown behavior protecting in-flight requests during deployments.
Port configuration is simple in principle—server.port—but production systems need explicit port declarations per environment rather than relying on defaults, plus validation that firewall rules and health endpoints align with whatever port is chosen. SSL/TLS configuration requires managing a Java KeyStore containing your certificate and private key, keeping that keystore out of version control, and locking down protocols to TLS 1.2 minimum while preferring TLS 1.3. The keystore password belongs in a secrets manager, never in a properties file.
Graceful shutdown is the most commonly forgotten of the three. Without server.shutdown=graceful and a properly sized spring.lifecycle.timeout-per-shutdown-phase, every deployment sends connection resets to clients and potentially rolls back in-flight database transactions. Size the shutdown timeout to at least 2x your p99 request duration, and align it with your Kubernetes terminationGracePeriodSeconds so the kubelet does not SIGKILL your process before it has finished draining. All three of these—port, TLS, shutdown—are low-complexity to configure correctly and high-impact when misconfigured, making them worth treating as a mandatory part of any Spring Boot production checklist.
Category
Related Posts
Spring Boot Build Tools: Maven & Gradle
Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.
Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow
Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.
JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests
Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.