Índice

  1. Qué es un servidor integrado y cómo lo inicia Spring Boot
  2. Servidores disponibles y cómo cambiarlos
  3. Configuración del puerto y modos de binding
  4. SSL, HTTP/2 y compresión de respuestas
  5. Personalización programática con WebServerFactoryCustomizer
  6. Proxy inverso y headers de reenvío
  7. Conclusión

1. Qué es un servidor integrado y cómo lo inicia Spring Boot

Spring Boot incluye un servidor web como dependencia del classpath. Al arrancar la aplicación, Spring Boot detecta esa dependencia y levanta el servidor automáticamente, sin necesidad de un contenedor externo ni de generar un WAR desplegable.

Este comportamiento está implementado en la clase SpringApplication. Cuando el tipo de aplicación es web (determinado en función del classpath), se crea un ServletWebServerApplicationContext en lugar del contexto estándar. Ese contexto es el responsable de instanciar el WebServerFactory correspondiente y levantar el servidor antes de que el contexto termine de inicializarse.

Para deshabilitar este comportamiento sin cambiar las dependencias, se puede configurar el tipo de aplicación explícitamente:

spring.main.web-application-type=none

Con esa propiedad, Spring Boot sigue creando un ApplicationContext, pero no levanta ningún servidor.


2. Servidores disponibles y cómo cambiarlos

Spring Boot soporta tres servidores para el stack servlet y tres para el stack reactivo:

Stack Servidor Starter
Servlet Tomcat (default) spring-boot-starter-tomcat
Servlet Jetty spring-boot-starter-jetty
Servlet Undertow spring-boot-starter-undertow
Reactivo Reactor Netty (default) spring-boot-starter-reactor-netty
Reactivo Tomcat spring-boot-starter-tomcat
Reactivo Jetty spring-boot-starter-jetty

El starter spring-boot-starter-web incluye spring-boot-starter-tomcat transitivamente. Para usar Jetty en su lugar, hay que excluir Tomcat y declarar Jetty explícitamente:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Si se empaqueta como WAR para despliegue en contenedor externo, la dependencia del servidor debe declararse con <scope>provided</scope> para que no se incluya en el artefacto final.


3. Configuración del puerto y modos de binding

El puerto por defecto es 8080. Se puede cambiar con la propiedad server.port:

server.port=9090

También se acepta la variable de entorno SERVER_PORT, gracias al binding relajado que aplica Spring Boot al resolver propiedades.

Existen tres modos de configuración de puerto relevantes:

Valor de server.port Comportamiento
Número entero positivo Bind en ese puerto
0 Puerto libre asignado por el SO
-1 No bind; se crea WebApplicationContext sin servidor HTTP

Cuando se usa server.port=0, el puerto asignado no es conocido hasta que el servidor está corriendo. Para obtenerlo en runtime, se puede escuchar el evento WebServerInitializedEvent:

@Component
public class PortListener implements ApplicationListener<WebServerInitializedEvent> {
    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        int port = event.getWebServer().getPort();
        System.out.println("Puerto asignado: " + port);
    }
}

En tests con @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT), el puerto se puede inyectar directamente con @LocalServerPort:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MiIntegrationTest {

    @LocalServerPort
    int port;
}

@LocalServerPort es una meta-anotación de @Value("${local.server.port}"). No es válida fuera del contexto de tests, porque el valor solo está disponible después de que el servidor terminó de inicializarse.


4. SSL, HTTP/2 y compresión de respuestas

SSL

SSL se configura a través del namespace server.ssl.*. La forma más directa es con un KeyStore JKS:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=otro-secret

También se puede usar un certificado en formato PEM (PKCS#8):

server.port=8443
server.ssl.certificate=classpath:cert.crt
server.ssl.certificate-private-key=classpath:cert.key
server.ssl.trust-certificate=classpath:ca.crt

Una vez configurado SSL, el conector HTTP en el puerto 8080 deja de estar disponible. Spring Boot no soporta configurar ambos conectores (HTTP y HTTPS) únicamente a través de application.properties. Si se necesitan los dos simultáneamente, uno de ellos debe configurarse programáticamente (ver sección 5).

Para gestionar múltiples certificados o reutilizar configuración SSL entre varios componentes (servidor, cliente REST, etc.), se pueden definir SSL Bundles y referenciarlos por nombre:

server.ssl.bundle=mi-bundle

SNI

Tomcat y Netty soportan Server Name Indication (SNI), lo que permite asignar distintos bundles SSL a diferentes hostnames en el mismo servidor:

server.ssl.bundle=web
server.ssl.server-name-bundles[0].server-name=alt1.example.com
server.ssl.server-name-bundles[0].bundle=web-alt1

Jetty no soporta configuración explícita de SNI, aunque puede configurarlo automáticamente si se le proveen múltiples certificados.

HTTP/2

HTTP/2 se activa con:

server.http2.enabled=true

Spring Boot soporta tanto h2 (HTTP/2 sobre TLS) como h2c (HTTP/2 sobre TCP sin TLS). Si SSL no está habilitado, se usa h2c. La compatibilidad varía según el servidor:

Servidor h2c h2 Notas
Tomcat 11 Out of the box
Tomcat (nativo) Con libtcnative en el JVM library path
Jetty Requiere jetty-http2-server + dependencias ALPN
Reactor Netty Out of the box; soporte nativo con netty-tcnative-boringssl-static

Compresión de respuestas

La compresión HTTP está soportada por Jetty, Tomcat y Reactor Netty. Se activa con:

server.compression.enabled=true

Por defecto, solo se comprimen respuestas de al menos 2048 bytes, y solo para los content types: text/html, text/xml, text/plain, text/css, text/javascript, application/javascript, application/json y application/xml. Ambos umbrales son configurables con server.compression.min-response-size y server.compression.mime-types.


5. Personalización programática con WebServerFactoryCustomizer

Cuando las propiedades del namespace server.* no cubren el caso de uso, Spring Boot expone WebServerFactoryCustomizer<T> como punto de extensión. Al declarar un bean de ese tipo, Spring Boot lo invoca automáticamente durante la configuración del servidor.

El tipo genérico T determina a qué factory se aplica el customizer. Para Tomcat con stack servlet:

@Component
public class TomcatCustomizer
        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> {
            connector.setProperty("relaxedQueryChars", "|{}[]");
        });
    }
}

Los WebServerFactoryCustomizer declarados por auto-configuración tienen orden 0. Los beans de usuario se procesan después, a menos que se especifique un @Order explícito.

Las factories disponibles son:

Servidor Stack servlet Stack reactivo
Tomcat TomcatServletWebServerFactory TomcatReactiveWebServerFactory
Jetty JettyServletWebServerFactory JettyReactiveWebServerFactory
Reactor Netty - NettyReactiveWebServerFactory

Múltiples conectores en Tomcat

Un caso concreto donde WebServerFactoryCustomizer es necesario es la configuración de HTTP y HTTPS simultáneos. Spring Boot no soporta esto con propiedades, pero sí programáticamente:

@Configuration
public class TomcatMultiConnectorConfig {

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
        return tomcat -> tomcat.addAdditionalConnectors(httpConnector());
    }

    private Connector httpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(8080);
        return connector;
    }
}

En ese escenario, HTTPS se configura en application.properties sobre el puerto 8443, y el conector HTTP adicional se añade programáticamente.


6. Proxy inverso y headers de reenvío

Cuando la aplicación corre detrás de un proxy inverso o load balancer, la información de la request original (host, puerto, esquema) puede diferir de la que recibe la aplicación. Los proxies incluyen esa información en headers como X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host y X-Forwarded-Port, o en el header estándar Forwarded (RFC 7239).

Spring Boot expone la propiedad server.forward-headers-strategy para controlar cómo se procesan esos headers:

Valor Comportamiento
NONE Los headers de reenvío son ignorados (default fuera de cloud)
NATIVE El servidor (Tomcat, Jetty, Netty) procesa los headers de forma nativa
FRAMEWORK Spring Framework procesa los headers con ForwardedHeaderFilter (servlet) o ForwardedHeaderTransformer (reactivo)

En plataformas cloud soportadas, el default es NATIVE. En el resto, NONE.

Para Tomcat, se puede personalizar qué headers se usan como fuente de información de IP y protocolo:

server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header

También se puede restringir qué IPs se consideran proxies internos de confianza:

server.tomcat.remoteip.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}

Si se usa Tomcat con terminación TLS en el proxy, se debe configurar:

server.tomcat.redirect-context-root=false

Esto permite que el header X-Forwarded-Proto se procese antes de que se emitan redirecciones, evitando que la aplicación genere URLs http:// cuando debería generar https://.


7. Conclusión

Spring Boot delega la gestión del servidor web en una infraestructura de auto-configuración que opera sobre WebServerFactory. La mayor parte de los casos de configuración (puerto, SSL, HTTP/2, compresión, proxy) se resuelven con propiedades en application.properties. Para los casos que no tienen propiedad equivalente, WebServerFactoryCustomizer proporciona acceso directo a la factory del servidor sin necesidad de reemplazar la auto-configuración completa.

El comportamiento del servidor en producción depende en gran parte de dónde corre la aplicación: si hay un proxy delante, si se hace TLS termination externamente, si se necesita HTTP/2 nativo. Entender qué capa maneja cada responsabilidad evita configuraciones contradictorias o comportamientos inesperados con los headers de reenvío.