Deploy a production-ready Spring Boot file server on a free-tier VPS
This article walks through deploying a self-hosted file server on a VPS with a clean production setup: build the app, configure environment variables, run it with systemd, place Nginx in front, and secure the server with SSL and a firewall.
Deployment architecture
To deploy a Spring Boot file server on a VPS, run your application as a persistent systemd service, place Nginx in front as a reverse proxy, secure it with HTTPS using Let’s Encrypt, and lock down public access with a firewall. This setup gives you a production-ready self-hosted file server without relying on managed storage platforms.
Running your own file server is not only about saving on storage costs. It is also about control: you decide where files live, how uploads are handled, what gets exposed publicly, and how the server is secured.
For this guide, we will use a Spring Boot file server pattern similar to FiloraFS. The exact implementation does not matter much here. The deployment workflow applies to most Java backend file upload services, self-hosted file servers, and Spring Boot APIs running on a VPS.
What you will build
By the end of this tutorial, you will have a file server that:
- accepts file uploads through a backend API
- stores files on disk or in a mounted directory
- runs continuously on a VPS using systemd
- serves traffic through Nginx
- uses HTTPS with a free Let’s Encrypt certificate
- is protected by a basic firewall setup
Prerequisites
- A VPS with Ubuntu 22.04 or similar
- Java 25 on your local machine and server for FiloraFS-Lite v2.0.0; the generic Java 21 installation example below does not apply to this release.
- Git access to your project repository
- A domain name, if you want HTTPS with Nginx
- Your application jar built and ready to deploy with FiloraFS-Lite
Need a ready-made Spring Boot file server instead of building one from scratch? Explore FiloraFS-Lite boilerplate →
1. Prepare the application for production
Start by making sure your app has a clean production configuration. Avoid hardcoding secrets or server-specific paths. Use environment variables or a separate production config file instead.
A typical Spring Boot setup for a file server might look like this:
# application-prod.properties
spring.application.name=filorafs
server.port=8080
storage.local.path=/opt/filora/uploads
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
spring.datasource.url=jdbc:mysql://localhost:3306/filorafs
spring.datasource.username=filora_user
spring.datasource.password=${DB_PASSWORD}If your project does not use a database, remove the datasource block and keep only the storage and multipart settings.
For FiloraFS-Lite v2.0.0, no database is required. Use FILORAFS_STORAGE_PATH for persistent storage and FILORAFS_API_KEY for a strong deployment key; the generic storage.local.path and STORAGE_PATH examples below are not Lite configuration names. Local startup needs no .env file. See FiloraFS-Lite configuration for the exact properties and multipart limits.
2. Get a free VPS
For a low-cost test deployment, you can use a free-tier VPS from a cloud provider that supports Linux instances. Any machine with public IP access, SSH access, and enough storage for your files will work.
Once the VPS is ready, download the SSH key or note the login credentials so you can connect securely.
3. Connect to the server and install dependencies
SSH into the server and install the tools required to run your backend.
ssh -i your-key.key ubuntu@your-vps-ip
sudo apt update && sudo apt upgrade -y
sudo apt install openjdk-21-jdk nginx ufw -yIf you plan to build the application directly on the server, also install Git and Maven.
FiloraFS-Lite v2.0.0 includes Maven Wrapper, so skip the Maven installation below. With Java 25 installed, run ./mvnw clean verify (or .\mvnw.cmd clean verify on Windows) in the repository. The built artifact is target/filorafs-lite-2.0.0.jar; rename the deployed copy to filorafs.jar to use this guide’s example commands and service file.
sudo apt install git maven -y4. Upload the JAR and create an app directory
Create a clean folder for your application. Keeping the jar, logs, and environment file inside one directory makes maintenance easier.
sudo mkdir -p /opt/filora
sudo chown -R $USER:$USER /opt/filoraThen copy your jar file into that directory.
scp target/filorafs.jar ubuntu@your-vps-ip:/opt/filora/5. Add environment variables
Put your secrets and server-specific values in a dedicated
.env file. This keeps your service config simpler and
avoids exposing values in the codebase.
# /opt/filora/.env
DB_PASSWORD=your_db_password
SERVER_PORT=8080
STORAGE_PATH=/opt/filora/uploads6. Run the app manually first
Before creating a systemd service, verify that the jar starts correctly in the terminal.
cd /opt/filora
source .env
java -jar filorafs.jarIf the application starts without errors, you are ready to make it persistent.
7. Create a systemd service
systemd keeps the file server alive after reboot and restarts it automatically if the process crashes.
If you are deploying Spring Boot services regularly, production process management matters far more than local development convenience.
sudo nano /etc/systemd/system/filorafs.servicePaste the following configuration:
[Unit]
Description=FiloraFS File Server
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/opt/filora
EnvironmentFile=/opt/filora/.env
ExecStart=/usr/bin/java -jar /opt/filora/filorafs.jar
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetThen enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable filorafs
sudo systemctl start filorafs
sudo systemctl status filorafs8. Put Nginx in front of the app
Nginx is useful even for a simple Spring Boot backend. It gives you a stable public entry point, handles HTTPS termination, and improves production deployment reliability.
Create a new site config:
sudo nano /etc/nginx/sites-available/filorafsExample configuration:
server {
listen 80;
server_name yourdomain.com;
client_max_body_size 100M;
location / {
proxy_pass http://localhost: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;
}
}Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/filorafs /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx9. Add HTTPS with Let’s Encrypt
If your server is publicly accessible, SSL is not optional. Use Certbot to get a free certificate and let Nginx manage the HTTPS configuration.
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.comAfter setup, Certbot can renew certificates automatically.
10. Lock down the firewall
Use UFW to expose only the ports you actually need.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enableIf your backend should never be reached directly, do not expose port 8080 publicly. Let Nginx talk to the app internally on localhost only.
Common mistakes
Using localhost in production config
Inside a VPS, localhost is still fine for the app itself, but external clients should connect through your domain and Nginx.
Skipping file permissions
Make sure the service user can write to the upload directory, otherwise file storage will fail even if the app starts.
Exposing the app port directly
Let Nginx handle public traffic and keep the backend private whenever possible.