---
title: "How to host Spring Boot on a VPS with systemd and Nginx | StreetHosting"
description: "Host Spring Boot on an Ubuntu VPS: the right Java, the JAR as a systemd service, tuned JVM heap, Nginx with HTTPS, firewall and safe updates."
url: "https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps"
type: "page"
language: "en-US"
---

VPS · 9 min · Intermediate

Published on Sep 28, 2026 · Updated on Sep 28, 2026

# Spring Boot in production on a VPS: from JAR to domain with HTTPS

A Spring Boot application becomes a production service with a few pieces: the system Java, a dedicated user, a systemd unit and Nginx in front. See each step and how to size the JVM memory so the system does not kill the process.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Network, DNS and domains](https://streethosting.com.br/en/guides/topics/networking) [Deploying and running apps](https://streethosting.com.br/en/guides/topics/deploy) [Linux administration](https://streethosting.com.br/en/guides/topics/linux) [Certificates and HTTPS](https://streethosting.com.br/en/guides/topics/ssl)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=How%20to%20host%20Spring%20Boot%20on%20a%20VPS%20with%20systemd%20and%20Nginx&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps "Share on LinkedIn") [](https://wa.me/?text=How%20to%20host%20Spring%20Boot%20on%20a%20VPS%20with%20systemd%20and%20Nginx%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-spring-boot-on-vps "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps.md)

In this guide 8 sections

* [How the application is laid out](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#arquitetura)
* [Installing the right Java](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#java)
* [Building the JAR and preparing the server](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#jar)
* [Production configuration](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#configuracao)
* [systemd service and JVM memory](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#systemd)
* [Nginx, domain, HTTPS and firewall](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#nginx-https)
* [Updating and fixing errors](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#atualizar-erros)
* [Which VPS to choose](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#onde-rodar)

Quick answer

To **host Spring Boot on a VPS**, install the same Java version used in the build, copy the executable JAR to `/opt`, run it under a dedicated user through a systemd unit with automatic restart and a heap limit, and publish it through Nginx with HTTPS from Let's Encrypt. The application listens only on 127.0.0.1:8080 and the firewall allows only SSH, 80 and 443.

## How the application is laid out on the VPS[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#arquitetura)

Spring Boot packages the application and the web server into a single JAR. That simplifies hosting a lot: there is no application server to install, only Java. Everything else is infrastructure around it to keep the process alive and safely exposed.

| Piece                         | Role                                                                      | Port                            |
| ----------------------------- | ------------------------------------------------------------------------- | ------------------------------- |
| Nginx                         | Receives the traffic, terminates HTTPS and forwards it to the application | 80 and 443, public              |
| Spring Boot (embedded Tomcat) | Runs the application                                                      | 8080, only on 127.0.0.1         |
| systemd                       | Starts at boot, restarts on crash, keeps the logs                         | None                            |
| PostgreSQL or MySQL           | Local database                                                            | 5432 or 3306, only on 127.0.0.1 |
| UFW                           | Blocks everything except SSH, 80 and 443                                  | None                            |

If the application uses a relational database and it does not exist yet, install it first by following the guide on [PostgreSQL on an Ubuntu VPS](https://streethosting.com.br/en/guides/vps/install-postgresql-ubuntu-vps), which already creates a dedicated user and database.

## Installing the right Java[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#java)

Spring Boot 3 and 4 require Java 17 or newer. The rule that saves you headaches is simple: the VPS runs the same major version used to compile. Check the `java.version` property in the `pom.xml` or the Gradle toolchain. Ubuntu 24.04 ships versions 17, 21 and 25 in the default repository, so there is no need to add an external source.

`sudo apt update sudo apt install -y openjdk-21-jre-headless java -version`

The `jre-headless` package has only what is needed to run, with no graphics libraries. If you intend to compile on the VPS itself, install the JDK (`openjdk-21-jdk-headless`). The most common approach, and the lightest for the server, is to compile on your computer or in CI and send only the JAR.

## Building the JAR and preparing the server[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#jar)

In the project, build the executable JAR with the build tool's wrapper:

`# Maven: produces target/minha-api-0.0.1-SNAPSHOT.jar ./mvnw clean package -DskipTests # Gradle: produces build/libs/minha-api-0.0.1-SNAPSHOT.jar ./gradlew bootJar`

With Gradle, watch out for the file ending in `-plain.jar`: it has no dependencies and does not run on its own. The right one is the other file, produced by the bootJar task.

On the VPS, create a system user with no shell for the application and the folder where the JAR will live. Running as root means any flaw in the application becomes full control of the server.

`sudo useradd --system --home-dir /opt/minha-api --shell /usr/sbin/nologin spring sudo mkdir -p /opt/minha-api /etc/minha-api sudo chown spring:spring /opt/minha-api`

Send the JAR from your computer and install it with the right owner and permissions. The `install` command copies and adjusts everything in one go. More transfer options, including rsync, are in the guide on [transferring files to the VPS](https://streethosting.com.br/en/guides/vps/transfer-files-to-vps-scp-rsync).

`# on your computer scp target/minha-api-0.0.1-SNAPSHOT.jar usuario@IP_DA_VPS:/tmp/app.jar # on the VPS sudo install -o spring -g spring -m 640 /tmp/app.jar /opt/minha-api/app.jar sudo -u spring java -jar /opt/minha-api/app.jar --server.address=127.0.0.1`

This manual test confirms the JAR starts with the installed Java. When the log shows that Tomcat started on port 8080, test from another terminal with `curl -i http://127.0.0.1:8080/actuator/health` (if the project uses Actuator) and stop it with Ctrl+C.

## Production configuration without touching the code[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#configuracao)

Spring Boot reads properties from environment variables by converting the names: `server.port` becomes `SERVER_PORT`, `spring.datasource.url` becomes `SPRING_DATASOURCE_URL`. That lets you keep passwords out of the repository, in a file only root can read:

`sudo nano /etc/minha-api/minha-api.env SPRING_PROFILES_ACTIVE=prod SERVER_ADDRESS=127.0.0.1 SERVER_PORT=8080 SERVER_FORWARD_HEADERS_STRATEGY=native SPRING_DATASOURCE_URL=jdbc:postgresql://127.0.0.1:5432/minha_api SPRING_DATASOURCE_USERNAME=minha_api SPRING_DATASOURCE_PASSWORD=troque-esta-senha sudo chmod 600 /etc/minha-api/minha-api.env`

* **SERVER\_ADDRESS=127.0.0.1:** port 8080 stops existing for the internet. Even if the firewall is switched off by mistake, nobody outside reaches the application without going through Nginx.
* **SERVER\_FORWARD\_HEADERS\_STRATEGY=native:**makes Tomcat honor the headers Nginx sends with the visitor's real IP and the original protocol. Without it, redirects go out as http and the logs always show 127.0.0.1.
* **prod profile:** activates the project's `application-prod.properties` file, if it exists, to switch off development features.

Two settings already come in good shape in current versions. Graceful shutdown is the default: on receiving the stop signal, the server stops accepting new connections and finishes the ones in flight, with a 30 second deadline per phase (adjustable in `spring.lifecycle.timeout-per-shutdown-phase`). And Actuator exposes only the health endpoint over HTTP; if you open others, such as metrics or env, block the `/actuator` path in Nginx.

### Database connection pool[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#pool-conexoes)

Spring Boot uses HikariCP as the connection pool, with at most 10 connections by default. PostgreSQL, in turn, accepts 100 simultaneous connections in its default configuration. With a single application there is plenty of room. The problem shows up when you bring up several applications, or several instances of the same one, pointing at the same database: the sum of the pools has to stay below the database limit, with headroom for maintenance connections. Adjust it through the environment file with `SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE`. Raising the pool rarely makes the API faster; on a VPS with few vCPUs, many simultaneous connections just compete for the same processor.

## systemd service and JVM memory[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#systemd)

The systemd unit plays the role a process manager would: it starts at boot, restarts on failure and sends the output to the journal.

`sudo nano /etc/systemd/system/minha-api.service [Unit] Description=Minha API Spring Boot After=network-online.target postgresql.service Wants=network-online.target [Service] User=spring Group=spring WorkingDirectory=/opt/minha-api EnvironmentFile=/etc/minha-api/minha-api.env ExecStart=/usr/bin/java -Xms256m -Xmx768m -XX:+ExitOnOutOfMemoryError -jar /opt/minha-api/app.jar SuccessExitStatus=143 Restart=on-failure RestartSec=5 TimeoutStopSec=45 [Install] WantedBy=multi-user.target`

`SuccessExitStatus=143` is there because the JVM exits with code 143 when it receives the stop signal. Without that line, every `systemctl stop` would be recorded as a failure. `TimeoutStopSec` gives the graceful shutdown time to finish before systemd forces it.

The `-XX:+ExitOnOutOfMemoryError` option fixes a treacherous problem. When the heap runs out, the JVM does not stop: it throws OutOfMemoryError in the thread that tried to allocate and stays alive, often with broken connection pools or schedulers, returning errors for part of the requests. With the option, the process exits immediately and `Restart=on-failure` brings up a clean instance in five seconds. You lose the in-memory state, but you trade a silent failure for one that shows up in the log.

`sudo systemctl daemon-reload sudo systemctl enable --now minha-api systemctl status minha-api journalctl -u minha-api -f`

### How much heap to give the JVM[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#memoria-jvm)

Without `-Xmx`, the JVM uses a quarter of the machine's RAM as the heap ceiling. On a small VPS that may be too little for the application; on a VPS shared with the database, it may be too much once added to the rest. Remember that the Java process consumes a few hundred MB beyond the heap (metaspace, threads, code cache), and the database and the system also need room. The values below are starting points to adjust after measuring.

| VPS RAM | What runs alongside                      | Suggested starting heap |
| ------- | ---------------------------------------- | ----------------------- |
| 2 GB    | Only the application                     | \-Xmx768m               |
| 4 GB    | Application and PostgreSQL               | \-Xmx1536m              |
| 8 GB    | Application, database and Redis          | \-Xmx3g                 |
| 16 GB   | Two or three applications and a database | \-Xmx3g for each one    |

If the total exceeds the RAM, the kernel picks a process to kill, and it is usually the JVM, the biggest one. The symptom is the application restarting on its own with no error in its log. Confirm with `sudo dmesg | grep -i kill` and reduce the heap or add memory.

## Nginx, domain, HTTPS and firewall[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#nginx-https)

Create the domain's A record, for example a subdomain for the API, pointing to the VPS IP, as described in the guide on [pointing a domain to the VPS](https://streethosting.com.br/en/guides/vps/point-domain-to-vps). Then configure Nginx:

`sudo apt install -y nginx sudo nano /etc/nginx/sites-available/minha-api server { listen 80; listen [::]:80; server_name api.seu-dominio.com.br; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; } }`

Enable the site, open only what is needed in the firewall and issue the certificate:

`sudo ln -s /etc/nginx/sites-available/minha-api /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d api.seu-dominio.com.br`

Never open 8080 in the firewall. The `Nginx Full` rule allows 80 and 443, which is all the public needs. The details on rules, profiles and how not to lock yourself out are in the guide on the [UFW firewall](https://streethosting.com.br/en/guides/vps/ufw-firewall-ubuntu-vps), and HTTPS renewal and security headers are in the guide on [SSL certificates with Nginx](https://streethosting.com.br/en/guides/vps/lets-encrypt-ssl-certificate-vps).

The `client_max_body_size` in Nginx is only half the story for uploads. Spring limits them by default to 1 MB per file and 10 MB per request; adjust `spring.servlet.multipart.max-file-size` and `max-request-size` together.

## Updating the version and fixing common errors[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#atualizar-erros)

Updating means swapping the JAR and restarting. Always keep the previous version so you can roll back in seconds if something goes wrong:

`sudo cp /opt/minha-api/app.jar /opt/minha-api/app-anterior.jar sudo install -o spring -g spring -m 640 /tmp/app.jar /opt/minha-api/app.jar sudo systemctl restart minha-api journalctl -u minha-api -n 50 --no-pager`

During startup, which takes from a few seconds to a few dozen seconds depending on the size of the application, Nginx answers 502. Eliminating that window requires running two instances on different ports and switching between them in Nginx, which is already pipeline territory. The guide on [deploying with GitHub Actions](https://streethosting.com.br/en/guides/vps/github-actions-deploy-to-vps) shows how to automate the upload and the restart.

The application logs go to the systemd journal, which on Ubuntu is persisted to disk and survives reboots. To investigate a problem, filter by period with `journalctl -u minha-api --since "1 hour ago"`. An application that logs a lot can fill the disk over time: check the space used with `journalctl --disk-usage` and, if needed, set a ceiling with `SystemMaxUse=500M` in the `/etc/systemd/journald.conf` file, followed by `sudo systemctl restart systemd-journald`.

| Error                                         | Cause                                                   | How to fix                                                                  |
| --------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------- |
| UnsupportedClassVersionError                  | JAR compiled with a newer Java than the one installed   | Install the same major version used in the build                            |
| no main manifest attribute                    | Running the plain JAR instead of the executable one     | Use the JAR produced by bootJar or by the Spring Boot plugin's package goal |
| Port 8080 was already in use                  | Another instance or another service on the port         | Check with sudo ss -tlnp and stop the old process                           |
| Application restarts on its own with no error | System killing the JVM for lack of memory               | Check dmesg and reduce -Xmx or add RAM                                      |
| Connection refused to the database at startup | Database still starting or listening on another address | Check the unit's After= and the address in the JDBC URL                     |
| Redirect to http                              | Proxy headers ignored                                   | Set SERVER\_FORWARD\_HEADERS\_STRATEGY=native                               |
| 413 Request Entity Too Large                  | Nginx body limit                                        | Raise client\_max\_body\_size and the multipart limits                      |

## Which VPS to choose for Spring Boot[](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#onde-rodar)

Java applications need two things from a VPS. Memory, because the heap and the JVM overhead are reserved up front. And a fast CPU, because startup and the JIT compilation of the first requests are intense: the higher the clock, the shorter the unavailability window on each deploy. The [Ryzen 9 9950X VPS](https://streethosting.com.br/en/vps/ryzen), with up to 5.7 GHz and DDR5, is the main recommendation. For several microservices with large thread pools, the [Xeon VPS](https://streethosting.com.br/en/vps/xeon) delivers more vCPU for the money, at a lower clock.

| Scenario                                    | Suggested plan                        | Monthly price |
| ------------------------------------------- | ------------------------------------- | ------------- |
| One API with heap up to 1.5 GB              | Ryzen 2 vCPU, 4 GB DDR5, 40 GB NVMe   | R$ 66.00      |
| API and PostgreSQL on the same VPS          | Ryzen 4 vCPU, 8 GB DDR5, 80 GB NVMe   | R$ 118.00     |
| Two or three applications and a database    | Ryzen 6 vCPU, 16 GB DDR5, 160 GB NVMe | R$ 222.00     |
| Microservices with many threads             | Xeon 6 vCPU, 8 GB DDR4, 80 GB NVMe    | R$ 77.00      |
| Several Java services and a bigger database | Xeon 9 vCPU, 16 GB DDR4, 160 GB NVMe  | R$ 145.00     |

Both lines are hosted in São Paulo, with Anti-DDoS included, NVMe storage, root access and activation within 60 seconds. If the JVM's consumption grows, the memory upgrade is done from the control panel, charges only the prorated difference for the cycle and requires a VM restart; after that, just raise `-Xmx` in the systemd unit.

* Same Java major version as the build
* Dedicated system user with no shell
* Passwords in an environment file with permission 600
* Application listening only on 127.0.0.1
* systemd unit with SuccessExitStatus=143, -Xmx and ExitOnOutOfMemoryError
* Nginx with Forwarded headers and HTTPS
* Previous JAR kept for rollback

In this guide

* [How the application is laid out](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#arquitetura)
* [Installing the right Java](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#java)
* [Building the JAR and preparing the server](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#jar)
* [Production configuration](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#configuracao)
* [systemd service and JVM memory](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#systemd)
* [Nginx, domain, HTTPS and firewall](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#nginx-https)
* [Updating and fixing errors](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#atualizar-erros)
* [Which VPS to choose](https://streethosting.com.br/en/guides/vps/host-spring-boot-on-vps#onde-rodar)

## Frequently asked questions

Which Java version should I use for Spring Boot?

Spring Boot 3 and 4 require Java 17 or newer. Use on the VPS the same major version the project is compiled with; Ubuntu 24.04 offers versions 17, 21 and 25 in the default repository, and 21 is a safe choice when nothing else dictates otherwise.

Do I need to install Tomcat on the VPS?

No. The Spring Boot executable JAR already ships with the embedded web server, Tomcat by default. A separately installed Tomcat only makes sense for legacy applications packaged as a WAR.

How much RAM does a Spring Boot application need?

A small REST API usually runs fine with a heap between 256 and 768 MB, but the Java process uses a few hundred MB beyond the heap. In practice, 2 GB covers the application on its own and 4 GB covers application and database on the same VPS; measure the real consumption after a few days.

Can I run several Spring Boot applications on the same VPS?

Yes. Create one systemd unit per application, each listening on a different port on 127.0.0.1, and one Nginx server block per domain. The math that matters is the sum of the heaps plus each JVM's overhead, which has to fit in RAM with room to spare.

Is Docker or systemd better for Spring Boot?

For a single JAR, systemd is simpler and adds no layer at all. Docker pays off when you already have an image built in CI, several services to orchestrate, or want the same environment on any machine.

Next step

See Ryzen VPS

Ryzen 9 9950X VPS in São Paulo with root access, NVMe and gamer Anti-DDoS.

[See Ryzen VPS](https://streethosting.com.br/en/vps/ryzen)

[See Xeon VPS Xeon VPS for steady workloads, automation and long-running projects.](https://streethosting.com.br/en/vps/xeon)

## Related guides

[VPS Intermediate How to install PostgreSQL on an Ubuntu VPS securely PostgreSQL is the most complete open source relational database and the default for many modern applications. Learn how to install it on an Ubuntu VPS, create a user and database for your application, understand authentication, allow remote access without exposing the port, schedule backups and tune memory. 10 min Read guide](https://streethosting.com.br/en/guides/vps/install-postgresql-ubuntu-vps) [VPS Intermediate How to set up Nginx as a reverse proxy on a VPS Your app runs on an internal port and you want to serve it on a domain with HTTPS. Nginx as a reverse proxy solves that and also brings several apps together in one place. 3 min Read guide](https://streethosting.com.br/en/guides/vps/nginx-reverse-proxy-vps) [VPS Beginner UFW on Ubuntu VPS: firewall rules without losing SSH UFW makes the Ubuntu firewall simpler, but one rule in the wrong order locks you out of your VPS. Learn how to enable it without losing SSH, open only what you need, deal with Docker, and get back in through the console if something goes wrong. 10 min Read guide](https://streethosting.com.br/en/guides/vps/ufw-firewall-ubuntu-vps)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
