Docs

Documentation versions (currently viewingVaadin 25)
Documentation translations (currently viewingEnglish)

Deploy Behind a Proxy

Run a Vaadin application behind a reverse proxy that handles TLS, let server push through it, and keep users on the same instance.

In production, a reverse proxy or a load balancer usually sits between the browsers and the application. It terminates TLS, and can serve several applications under one domain, or distribute requests over several instances. For a Vaadin application, the proxy has to:

  • Tell the application which scheme and host the browser used.

  • Let the WebSocket connections of server push through.

  • Keep idle push connections open.

  • Send all requests of a user to the same instance, if there are several.

The following nginx configuration does the first three for a single instance:

Source code
nginx.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    server_name app.example.com;
    ssl_certificate     /etc/nginx/certs/cert.pem;
    ssl_certificate_key /etc/nginx/certs/key.pem;

    location / {
        proxy_pass http://app:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 300s;
    }
}

Then tell the application to trust the forwarded headers:

Source code
application-prod.properties
server.forward-headers-strategy=native

The Reverse Proxies reference has configurations for Apache HTTPD, for other deployment scenarios, for load balancing, and for several applications behind one proxy.

Tell the Application About TLS

When the proxy terminates TLS, the application receives plain HTTP requests. The proxy passes the original host in the Host header, the scheme in the X-Forwarded-Proto header, and the address of the browser in the X-Forwarded-For header. The server.forward-headers-strategy=native property makes the application use them.

Without it, the application thinks that the browser uses HTTP. For example, it redirects users who aren’t logged in to http:// instead of https://, and sends the session cookie without the Secure attribute.

Spring Boot enables forwarded headers by default when it detects a supported cloud platform. See the Spring Boot documentation for details.

Let Server Push Through

With server push, the browser opens a WebSocket connection to the /VAADIN/push path. A WebSocket connection starts as an HTTP request, which asks the server to upgrade the connection with the Upgrade and Connection headers. nginx doesn’t forward these headers unless you tell it to, as the proxy_set_header lines in the example do. Other proxies have similar settings, which the Reverse Proxies reference describes for Apache HTTPD.

If the proxy doesn’t forward them, the request fails with status 501, and the browser tries again every few seconds. After a while, it falls back to long polling, which works, but needs a new request after every message from the server. Because the application keeps working, the problem is easy to miss. To check that the WebSocket connection works, look at the request to /VAADIN/push in the browser’s developer tools, or in the proxy’s access log. Its status should be 101, which means that the connection was upgraded.

If you can’t make the proxy forward the upgrade, set the push transport to long polling. The browser then uses long polling from the start, instead of trying a WebSocket connection first:

Source code
Java
@Push(transport = Transport.LONG_POLLING)
public class Application implements AppShellConfigurator {
}

To use long polling only in the deployments that need it, set the transport in a UI init listener instead, with ui.getPushConfiguration().setTransport(Transport.LONG_POLLING).

Keep Push Connections Open

Proxies close connections that have been idle for too long, and a push connection can be idle for minutes. The example raises the 60-second default of nginx to 300 seconds with proxy_read_timeout. Load balancers in cloud platforms have similar idle timeouts. See Push Connection Timeout for why 60 seconds isn’t enough.

If you can’t raise the idle timeout — for example, in a load balancer that you don’t control — make the push connection send its heartbeat more often instead. By default, it sends one every 60 seconds. The interval is a parameter of Atmosphere, the library that Vaadin uses for push, and you set it as an init parameter of the Vaadin servlet. Add the following bean to a configuration class:

Source code
Java
@Bean
static BeanPostProcessor pushHeartbeat() {
    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean,
                String beanName) {
            if (bean instanceof ServletRegistrationBean<?> registration
                    && registration.getServlet() instanceof SpringServlet) {
                registration.addInitParameter(
                        "org.atmosphere.interceptor.HeartbeatInterceptor.heartbeatFrequencyInSeconds",
                        "15"); 1
            }
            return bean;
        }
    };
}
  1. Use an interval that’s shorter than the idle timeout of the proxy.

Each push connection then carries a small message at that interval, so the proxy doesn’t consider it idle.

Send Each User to the Same Instance

If you run several instances of the application, the load balancer has to send all requests of a user to the instance that holds their session. This is called sticky sessions or session affinity. Without it, users get unexpected reloads, and lose their unsaved work. See Load Balancing with Sticky Session for nginx and Apache HTTPD configurations, and Test What Users See When a Server Stops for what happens when an instance stops.

On Kubernetes, the Ingress NGINX controller has been retired. Its replacement, the Gateway API, doesn’t require its implementations to support session persistence, so choose one that does. The Kubernetes Kit guide uses Envoy Gateway.

Next Steps

The proxy and the container platform need to know whether the application is running and ready for requests. Add Health Checks shows which URLs to give them.