Java and Spring Boot apps run as long-lived JVM processes, so they belong on a server where you control the process and the memory. This guide deploys a Spring Boot app on a Domain India VPS: installing a JDK, running the JAR under systemd with a dedicated user, putting nginx and a free Let's Encrypt certificate in front, connecting PostgreSQL, and sizing the JVM heap.
Build your app as an executable JAR, install an LTS JDK (Java 21 or 25) on the VPS, run the JAR under systemd as its own user, and put nginx with Let's Encrypt in front of it. Keep the JVM heap to about half the server's RAM. Shared hosting can't run a JVM, so use a VPS.
1. Why a VPS, not shared hosting
A Spring Boot app is a JVM process that stays running and typically uses anywhere from 256 MB to several GB of RAM. Shared hosting is built for request/response apps: on Domain India shared hosting, long-running processes are stopped, and there is no Java app tool. A Domain India VPS gives you root access, persistent processes and dedicated RAM.
Rough sizing:
| App size | Server RAM | JVM heap (-Xmx) |
|---|---|---|
| Small API or MVP | 2 GB | 512m to 1g |
| Production API | 4 GB | about 2g |
| Several services | 8 GB or more | 1g to 4g per service |
Real needs depend on your app; measure memory under load before you choose.
2. Install the JDK
Java 25 (September 2025) and Java 21 are the current long-term-support (LTS) releases. Java 21 is the safe choice for Spring Boot 3.x apps; Spring Boot 4 also supports Java 25. Java 21 is shown here because every current distribution packages it.
- Connect as root.SSH into your VPS with your key.
- Install OpenJDK.Run the command for your OS (below).
- Verify.
java -versionshould show version 21 (or 25). - Create an app user.Run
sudo useradd -r -s /sbin/nologin -m -d /opt/spring-app spring.
# AlmaLinux / Rocky Linux
sudo dnf install -y java-21-openjdk-headless
# Ubuntu 24.04
sudo apt install -y openjdk-21-jdk-headlessNon-LTS releases (such as Java 24 or 26) get updates for only six months. Stick to LTS for production. Distribution OpenJDK packages receive security updates through normal OS updates, so keep the server patched. If your distribution doesn't package Java 25 yet, Eclipse Temurin provides builds.
3. Build and upload the JAR
On your own computer:
./mvnw clean package -DskipTests
# Produces target/your-app-0.0.1-SNAPSHOT.jarUpload it:
scp target/your-app-0.0.1-SNAPSHOT.jar root@your-vps:/opt/spring-app/app.jar
ssh root@your-vps 'chown spring:spring /opt/spring-app/app.jar && mkdir -p /opt/spring-app/heapdumps && chown spring:spring /opt/spring-app/heapdumps'4. Run it under systemd
Create /etc/systemd/system/spring-app.service:
[Unit]
Description=Spring Boot Application
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=spring
Group=spring
WorkingDirectory=/opt/spring-app
ExecStart=/usr/bin/java \
-Xms256m -Xmx1024m \
-XX:+UseG1GC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/opt/spring-app/heapdumps \
-Dspring.profiles.active=production \
-Dserver.port=8080 \
-Dserver.address=127.0.0.1 \
-jar /opt/spring-app/app.jar
SuccessExitStatus=143
TimeoutStopSec=20
Restart=on-failure
RestartSec=10
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/spring-app
ProtectHome=true
# Environment
EnvironmentFile=-/opt/spring-app/.env
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetBinding to 127.0.0.1 keeps the app reachable only through nginx.
Create /opt/spring-app/.env, then lock it down with chmod 600 and chown spring:spring:
DB_URL=jdbc:postgresql://localhost:5432/myapp
DB_USER=spring
DB_PASSWORD=use-a-long-random-password
JWT_SECRET=use-a-long-random-secretStart it:
sudo systemctl daemon-reload
sudo systemctl enable --now spring-app
sudo journalctl -u spring-app -f5. nginx reverse proxy and SSL
Create /etc/nginx/conf.d/spring.conf:
upstream spring {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name api.yourcompany.com;
client_max_body_size 20M;
location / {
proxy_pass http://spring;
proxy_http_version 1.1;
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_set_header Connection "";
proxy_read_timeout 60s;
}
}Tell Spring it is behind a proxy by adding server.forward-headers-strategy=framework to your production properties, so redirects and generated links use https.
Test, reload and get a certificate (on AlmaLinux, certbot comes from the EPEL repository):
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourcompany.comPoint the domain's A record at your VPS IP before running certbot.
On AlmaLinux and Rocky Linux, SELinux blocks nginx from connecting to your app until you allow it once with sudo setsebool -P httpd_can_network_connect 1. Without it, nginx returns a 502 Bad Gateway.
6. Database (PostgreSQL)
# AlmaLinux / Rocky Linux
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo -u postgres psql -c "CREATE USER spring WITH PASSWORD 'use-a-long-random-password';"
sudo -u postgres psql -c "CREATE DATABASE myapp OWNER spring;"Making spring the database owner avoids the "permission denied for schema public" error that PostgreSQL 15 and later give when you only GRANT ALL ON DATABASE.
On AlmaLinux and Rocky Linux, the default pg_hba.conf uses ident authentication for connections to 127.0.0.1, so a JDBC login with a password fails. Change those host lines to scram-sha-256 in /var/lib/pgsql/data/pg_hba.conf, then run sudo systemctl reload postgresql.
application-production.yml in your Spring app:
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 10
jpa:
hibernate:
ddl-auto: validate # use Flyway or Liquibase for schema changes; never create in production
show-sql: falseHibernate 6 detects the PostgreSQL dialect on its own, so you don't need to set it.
7. Memory tuning
Wrong heap sizing is the most common cause of Spring Boot crashes on a small server.
| Server RAM | -Xms | -Xmx | Left for the OS, metaspace and threads |
|---|---|---|---|
| 2 GB | 256m | 1024m | About 1 GB |
| 4 GB | 512m | 2048m | About 2 GB |
| 8 GB | 1024m | 4096m | About 4 GB |
Rule of thumb: maximum heap no more than half the total RAM, less if PostgreSQL runs on the same server. The JVM also uses memory outside the heap (metaspace, thread stacks, native buffers). An oversized heap leads to the Linux OOM killer ending the process, and systemd restarting it in a loop.
Keep -XX:+HeapDumpOnOutOfMemoryError so that a crash leaves a dump you can open in Eclipse MAT.
On Java 21 and later, spring.threads.virtual.enabled=true (Spring Boot 3.2+) lets Tomcat use virtual threads, which handles many slow I/O requests without raising the thread count.
8. Build tips
Parallel Maven builds for multi-module projects:
./mvnw -T 4 clean package -DskipTests-T 4 uses four build threads.
Layered JARs for container images: Spring Boot builds layered JARs by default. If you later package the app as a container, extract the layers so dependency layers are cached and only your code layer changes between builds:
java -Djarmode=tools -jar app.jar extract --layers --launcher9. Common pitfalls
10. Running Java on Domain India
- VPS: the right place for Java. Domain India VPS plans are self-managed, with full root access on KVM virtualisation and NVMe storage. You install and patch the JDK, nginx and PostgreSQL yourself. Plans start from ₹553 a month, excluding 18% GST (Domain India list price on 30 September 2026). No backups or snapshots are included, so schedule your own database dumps and copy them off the server.
- App Platform: runs any language from your own Dockerfile, deployed from GitHub (Deploy Now) or with a deploy token, and includes PostgreSQL and free SSL on every plan. It has no SSH access, no Redis and no WebSocket support. Check your app's memory needs against the plan before choosing it for a JVM app.
- Shared hosting: not suitable for Java; long-running processes are stopped.
Which JDK should I use: OpenJDK, Oracle JDK or GraalVM?
An OpenJDK build (from your distribution or Eclipse Temurin) is the usual choice for production. Oracle JDK has its own licence terms, so read them before using it commercially. GraalVM native images start in milliseconds and use less memory, but builds are slower and reflection-heavy code needs extra configuration.
Should I use a native image or the normal JVM?
Start with the normal JVM: it is easier to build and debug. Consider a GraalVM native image when start-up time or memory is critical, for example many small services on one server.
Tomcat, Netty or Undertow?
Spring Boot uses embedded Tomcat by default, which suits most apps. Netty is used by the reactive WebFlux stack. Switching servers rarely matters as much as heap size and connection pool settings.
Should I enable HTTPS directly in Spring Boot?
Usually not. Let nginx terminate SSL with a free Let's Encrypt certificate; it is simpler to renew and tune.
Can I run several Spring services on one VPS?
Yes. Give each its own port, systemd service and nginx server block, and plan the heap for each so that the total stays well under the server's RAM.
Can I run a Spring Boot app on Domain India shared hosting?
No. Shared hosting stops long-running processes and has no Java app tool. Use a Domain India VPS, or the App Platform with your own Dockerfile.
Ready to deploy? Choose a VPS plan for full control, look at the App Platform for Dockerfile deploys with PostgreSQL included, or ask us through a support ticket.
Full root access, dedicated RAM and your choice of JDK and database.
See VPS plans