Spring Boot With Nginx
Sometimes when deploying a Spring Boot application on a server, we use another piece of software. An example of that piece of software is Nginx.
There are many reasons why someone would use Nginx together with a Spring Boot application, like:
- simple deployment
- serves as a reverse proxy
- handles SSL connections
- can be used as a load balancer
- already handles compression
- can serve as another layer of security with basic auth, IP allowance or denial, IP blocking with GeoIP
- and many more
On an ubuntu server after Nginx is installed, Nginx configurations can be added to a folder /etc/nginx/sites-available
. On an ubuntu server after Nginx is installed, Nginx configurations can be added to a folder /etc/nginx/sites-available
. So let's create a simple configuration that will set up Nginx as a reverse proxy handling requests for a domain example.org
with a Spring Boot app running on our server.
upstream example_org_backend {
server localhost:8081;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name example.org;
access_log /var/log/nginx/example.org.access.log;
error_log /var/log/nginx/example.org.error.log;
location / {
return 301 https://example.org$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.org;
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam /etc/nginx/dh.pem;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /etc/letsencrypt/live/example.org/chain.pem;
# replace with the IP address of your resolver
resolver 1.1.1.1;
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_set_header Connection "keep-alive";
proxy_store off;
proxy_pass http://example_org_backend;
}
}
Our config file contains three main sections:
- definition of our upstream server (i.e. our Spring Boot app)
- server listening on port 80
- server listening on port 443
Upstream directive
This directive specifies group of servers that will be passed to our proxy_pass
directive. If we specify more than one server, client requests will be distributed among all specified servers. With more than one server, we can use Nginx as a load balancer. keepalive
directive specifies how many connections should be kept alive. In our case, server represents Spring Boot app running on localhost
with port 8081
.
Server directive with port 80
This section sets up Nginx to listen on port 80 and for each request returns a 301
response to redirect user to the same website with the same request uri but on port 443
with SSL encryption enabled.
Server directive with port 443
This directive specifies our core logic for our Spring Boot app. Our example already contains standard paths to let's encrypt certificates in case we have it deployed on our server. Otherwise we can be change it to any other certificate we already have for our domain. SSL directives are actually used from a nice tool created by mozilla, available here.
location
directive specifies, that every request should be passed to our Spring Boot app specified by our proxy_pass
directive with our upstream
server. There are some things to keep in mind though, a generic Spring Boot application already has implemented parsing of X-Forwarded-*
headers, but needs to be enabled. You probably already use application.properties
file for your Spring Boot app, so you can simply append server.forward-headers-strategy=native
to it. With a value of native
, Spring Boot app uses tomcat
(tomcat
is used by default, if not changed) to parse headers to correctly set remote address available inside our requests. There is also an option framework
which sets up a new Spring Boot bean with a filter that handles X-Forwarded-*
headers instead of tomcat
. Without this settings, our app would show us a remote address of our request as 127.0.0.1
, which is not desired.
In our configuration we can set proxy_set_header X-Forwarded-For
as $proxy_add_x_forwarded_for
instead of $remote_addr
which appends $remote_addr
to existing X-Forwarded-For
header or creates a new header with that value. But since our app is directly facing public internet and not another proxy, we don't want any user to create his own requests with already set X-Forwarded-For
header with a custom ip address, so instead we overwrite it with current user's ip address. But keep in mind, every X-Forwarded-*
header used by our app needs to be set correctly, otherwise a user can write its own values for each header.
Final steps
Our config is done! Now we can do some final steps to apply our configuration:
# Let's change our current directory
cd /etc/nginx/sites-enabled
# Create a symlink to our config file
ln -s ../sites-available/example.org
# Test our configuration before applying
nginx -t
# Finally we can reload Nginx to apply our configuration
systemctl reload nginx
Conclusion
That's all! We created our Nginx configuration with our custom example.org domain with a Spring Boot backend app and deployed it successfully. Easy!