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.

published: reading time: 21 min read author: GeekWorkBench
Quick Summary

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 localhost only
  • 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 terminationGracePeriodSeconds allow?
# 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.port is 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-phase with terminationGracePeriodSeconds in 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

  1. Keep keystores out of version control – Add *.jks to .gitignore and use a secrets manager.
  2. Rotate certificates before they expire – Automated renewal avoids urgent renewals at midnight.
  3. Drop deprecated protocols – SSLv3, TLS 1.0, and TLS 1.1 have known vulnerabilities. Lock to TLS 1.2+.
  4. Use strong ciphers only – Your SSL config should not include anything from the 1990s.
  5. 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=true and point to your keystore
  • Enable graceful shutdown with server.shutdown=graceful
  • Match spring.lifecycle.timeout-per-shutdown-phase to your request patterns
  • Align the application shutdown timeout with your Kubernetes terminationGracePeriodSeconds
  • Keep keystores and passwords out of source control

Interview Questions

1. How do you change the default port in a Spring Boot application?

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.

2. What is the purpose of graceful shutdown in Spring Boot?

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.

3. How do you configure SSL/TLS in Spring Boot?

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.

4. What happens during Spring Boot's graceful shutdown phase?

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.

5. How do you handle SSL certificates in a Spring Boot application deployed to Kubernetes?

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.

6. What are the differences between Tomcat, Jetty, and Undertow as embedded servlet containers in Spring Boot?

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.

7. How does Spring Boot's embedded container get created and started during application startup?

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.

8. Why might you set 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.

9. What is the difference between 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.

10. What causes "Port 8080 already in use" at startup and how do you diagnose it?

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.

11. How do you redirect HTTP to HTTPS when both are configured in Spring Boot?

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.

12. What is the risk of keeping the default port 8080 in production?

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.

13. What happens if 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.

14. How does graceful shutdown interact with Kubernetes readiness and liveness probes?

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.

15. What are the security implications of storing SSL keystore passwords in application properties files?

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.

16. How does SSL/TLS impact performance in a servlet container?

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.

17. What is the purpose of 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.

18. How does 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.

19. What is the relationship between 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.

20. When would you choose Undertow over Tomcat for a Spring Boot application?

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

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.

#spring-boot #spring-boot-roadmap #learning-path

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.

#spring-boot #spring-boot-roadmap #learning-path

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.

#spring-boot #spring-boot-roadmap #learning-path