Free tools Windows power users keep installed
One-click scans. No signup required.
The dependable production path is Sinatra app → Puma → systemd → nginx → HTTPS. Puma supplies the Rack server process and concurrency that Sinatra intentionally leaves to an underlying handler; systemd keeps Puma running, nginx is the only public application entry point, and Certbot obtains and renews a Let’s Encrypt certificate. This layout also lets you expose only SSH, HTTP, and HTTPS at the VPS firewall while keeping Puma on loopback.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
The commands below use an example application directory at /srv/myapp, a Linux account named myapp, a Puma listener on 127.0.0.1:9292, and the domain example.com. Replace those values with your actual paths, account, port, and domain.
As an Amazon Associate I earn from qualifying purchases.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11What the production architecture does
Sinatra is a Rack application framework, not a complete production process manager. Sinatra’s project documentation says it does not impose a concurrency model and leaves that responsibility to the Rack handler, such as Puma or Falcon. In this deployment, Puma is the long-running Ruby process; systemd starts it, restarts it, and records its logs; nginx accepts internet traffic and proxies requests to Puma; and Let’s Encrypt provides HTTPS.
| Layer | Responsibility | Publicly reachable? |
|---|---|---|
| Sinatra | Your routes, middleware, views, and application logic | No |
| Puma | Rack server, worker/thread process, and local listener | No; bind to loopback or a private Unix socket |
| systemd | Foreground supervision, startup at boot, restart policy, and journaling | No |
| nginx | HTTP/TLS termination, proxying, and public port handling | Yes, on ports 80 and 443 |
| UFW | Host firewall policy | Controls which ports can be reached |
A loopback TCP listener is used in the examples because it is straightforward to inspect and avoids filesystem-permission issues. A Unix socket is also valid, but its directory ownership, path, and lifecycle must match both Puma and nginx.
#1 Best Overall
Requirements and decisions before you log in
Use a supported Ruby version
Sinatra 4.0.0 does not support Ruby versions earlier than 2.7.8. Select a stable Ruby version supported by your application and by the current Sinatra compatibility policy. Install it with the project’s chosen version manager or with the Ubuntu distribution method, then commit the version files your team uses and keep Gemfile.lock under version control. Do not assume the VPS’s preinstalled Ruby is suitable for the app.
Choose an application identity and directory
Create a dedicated, non-root account for the service. The account should own the release directory and the files it must write, but it should not have administrative privileges. A conventional layout is /srv/myapp. Keep secrets outside the Git checkout, ideally in a root-owned environment file readable by the service account only.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteDecide how releases will be updated
An in-place checkout is simplest for a small app: update the code, run Bundler, and restart Puma. A versioned-release layout with directories such as /srv/myapp/releases/2026-10-15-1200 and a current symlink makes rollback safer, but requires a deployment script that changes the symlink atomically and restarts the service. Either approach works with systemd; consistency matters more than the naming scheme.
1. Prepare the Ubuntu VPS securely
- Create a non-root administrator. Add an account with administrative escalation, install your SSH public key, and verify a second SSH session works before changing the original session. Keep one known-good administrative path open while you configure the server.
- Update the operating system. Apply the current Ubuntu package updates and reboot if the kernel or other core services require it. Reconnect over SSH after the reboot.
- Create the service account and directory. Create the
myappaccount, create/srv/myapp, and assign ownership to that account. Do not run the Ruby process as root. - Install the required server components. Install nginx, UFW, Git if you deploy from a repository, and Certbot with its nginx integration. Ruby and Bundler should come from the version strategy selected for the application.
- Record the VPS public address. You will need it when creating DNS records. Do not issue a certificate until the domain resolves to this address.
2. Install and pin Ruby and Bundler
Use one Ruby installation method consistently. A version manager is useful when several applications require different Ruby versions; an Ubuntu package is simpler when the host runs one application and the distribution version meets Sinatra and your gem requirements. Whichever method you choose, make the executable path visible to systemd rather than relying on an interactive shell’s profile files.
- Verify the selected interpreter with
ruby --version. - Verify Bundler with
bundle --version. - Keep the project’s Ruby version file, such as the one used by your version manager, in the repository.
- Deploy the committed
Gemfile.lockso production resolves the same dependency versions as the build environment.
If you generate a Bundler binstub, its absolute path is stable for systemd even when a login shell would otherwise need to initialize a version manager.
3. Deploy the Sinatra application
Copy code and install gems
Clone or copy the application into /srv/myapp, then run Bundler from that directory as the service account:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →cd /srv/myapp
bundle config set --local deployment true
bundle install
The deployment setting installs the locked dependency set and avoids development-only surprises. If your build process compiles native gems, perform that build with the same Ruby version that will run the service.
Provide a Rack entry point
Puma’s documented Sinatra path uses a config.ru file that requires the application and runs Sinatra::Application:
Rank #2
# config.ru
require './app'
run Sinatra::Application
If your application file has a different name or uses a modular Sinatra class, require the correct file and run that Rack application instead. Test the entry point from the project directory; a missing require path is one of the most common causes of a service that exits immediately.
Set production environment variables
Put database URLs, session secrets, API credentials, and other sensitive values in the service environment or a protected environment file. Never commit those values to the repository. Make the file readable only by root and the service account, and use the same variable names locally and in production so the application does not silently fall back to development defaults.
4. Run Puma manually before adding supervision
Start the app in the foreground from its directory. Using bundle exec guarantees that the locked Puma and Rack dependencies are selected:
cd /srv/myapp
bundle exec puma
If you use a Puma configuration file, pass its explicit path, for example bundle exec puma -C /srv/myapp/config/puma.rb. Puma also documents a direct Sinatra launch such as ruby app.rb -s Puma, but the Rack entry point is easier to make reproducible in a systemd service.
From another SSH session on the VPS, query the local listener. If you selected the default Puma port, try curl http://127.0.0.1:9292. Confirm that the expected response arrives, then stop Puma with Ctrl-C. Do not proceed to nginx until this local request works; otherwise the reverse proxy only hides the original startup problem.
5. Keep Puma running with systemd
Create a dedicated unit
Create /etc/systemd/system/myapp.service with a foreground Puma process, an explicit working directory, an absolute executable, a least-privilege user, and a restart policy:
[Unit]
Description=Sinatra application served by Puma
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
EnvironmentFile=-/etc/myapp/myapp.env
ExecStart=/srv/myapp/bin/puma -C /srv/myapp/config/puma.rb
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Generate the binstub used above from the application directory if needed:
cd /srv/myapp
bundle binstubs puma --path bin
Alternatively, set ExecStart to the absolute path of the Ruby and Puma executable supplied by your version-management strategy. Do not use a shell command that depends on an interactive profile, and do not daemonize Puma; systemd must retain the foreground process as its child.
Rank #3
Define Puma’s bind address
For the loopback layout, /srv/myapp/config/puma.rb can contain:
environment ENV.fetch('RACK_ENV', 'production')
bind 'tcp://127.0.0.1:9292'
workers Integer(ENV.fetch('WEB_CONCURRENCY', '1'))
threads_count = Integer(ENV.fetch('MAX_THREADS', '5'))
threads threads_count, threads_count
Choose worker and thread values based on your application’s memory use and workload rather than copying a universal number. The important deployment property is that Puma binds only to an internal address while nginx owns the public ports.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Load, start, and inspect the unit
- Reload unit definitions:
sudo systemctl daemon-reload. - Enable startup at boot:
sudo systemctl enable myapp.service. - Start Puma:
sudo systemctl start myapp.service. - Inspect its state:
sudo systemctl status myapp.service. - Read recent logs:
sudo journalctl -u myapp.service -n 100 --no-pager. - After a code or configuration release, restart it with
sudo systemctl restart myapp.service.
If you edit the unit file, run daemon-reload before restarting. If you edit only application files, a restart is normally sufficient. A service that repeatedly restarts should be debugged from its journal before nginx is configured.
6. Put nginx in front of Puma
Create the server block
Configure nginx with the real domain and the same internal Puma address:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:9292;
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;
}
}
Place the file in nginx’s available-sites directory, enable it with the distribution’s sites-enabled mechanism, and remove or disable an unwanted default server if it would catch the request first. Check the syntax and reload:
sudo nginx -t
sudo systemctl reload nginx
At this stage, an HTTP request should reach Puma once DNS points the domain at the VPS. Keep Puma bound to loopback; exposing port 9292 in the firewall defeats the separation between edge server and application process.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Unix socket alternative
A Unix socket can reduce TCP configuration and make the listener inherently local. Set Puma’s bind to a path such as unix:///run/myapp/puma.sock, create the runtime directory with ownership that permits both Puma and nginx to use the socket, and set nginx’s proxy_pass to that socket. Keep the path identical in every configuration. Puma’s systemd documentation warns that a release-path socket can differ from the socket path supplied by systemd; Puma may then attempt to bind a second time and report There is already a server bound to:. A loopback TCP bind is often the simpler first deployment.
7. Point DNS at the VPS and enable HTTPS
DNS prerequisites
Register the domain and create DNS records that resolve to the VPS public address. Include every hostname you intend to place on the certificate, such as the apex name and www. Wait for the records to resolve from outside the VPS before requesting a certificate. A certificate authority cannot complete HTTP validation if the name reaches another server.
Rank #4
Request a Let’s Encrypt certificate with Certbot
With nginx running on port 80, request the certificate through its nginx plugin:
sudo certbot --nginx -d example.com -d www.example.com
Let’s Encrypt certificates are valid for 90 days. The HTTP-01 challenge generally requires port 80 to be reachable while issuance is in progress. Certbot’s nginx plugin edits the matching nginx server block and reloads nginx after the certificate is installed. Review the proposed redirect and hostname choices before accepting them.
Verify automatic renewal
Ubuntu documents a systemd timer that attempts renewal twice daily. Confirm the timer exists and perform a safe renewal simulation:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
The dry run should complete without an error. If you terminate TLS in a service other than nginx, add an appropriate renewal hook so that service reloads its certificate. A renewed certificate is not useful if the process continues serving an old copy.
8. Configure UFW without locking yourself out
Allow SSH before enabling the firewall. Then allow the web ports required by nginx:
Recommended Free Tools
sudo ufw allow 22
sudo ufw insert 1 allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status
Adapt the SSH rule to your actual port and, where practical, restrict it to trusted source addresses. The normal public rules are SSH for administration and HTTP/HTTPS for the web server. Do not open Puma’s 9292 port when it is bound to loopback. Check your VPS provider’s network firewall as well as UFW; both layers must permit the intended traffic.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.9. Verify the complete request path
Check each layer separately so a failure has a narrow search area:
- DNS: Resolve the domain from an external network and confirm it returns the VPS address.
- Puma: Run
curl http://127.0.0.1:9292on the VPS and confirm the Sinatra response. - systemd: Check
sudo systemctl status myapp.serviceand inspectsudo journalctl -u myapp.servicefor boot and request errors. - nginx syntax: Run
sudo nginx -t, then reload nginx and inspectsudo systemctl status nginx. - HTTP edge: Request the domain over port 80 and confirm nginx reaches the app rather than its default page.
- TLS edge: Request
https://example.com, inspect the certificate hostname and expiry, and verify that the HTTP-to-HTTPS behavior matches your policy. - Renewal: Run
sudo certbot renew --dry-runafter the initial certificate works.
These checks distinguish DNS errors, a dead Puma process, an nginx routing mistake, and a firewall or certificate problem instead of treating every symptom as a Sinatra bug.
Best Value
Common failures and precise fixes
Systemd reports an executable or permission error
Check the exact path in ExecStart, verify the binstub is executable, and confirm the service account can traverse every parent directory. Version managers often install Ruby under a user directory that systemd cannot access unless the path is explicit and permissions allow it. Run the same command as the service account from WorkingDirectory to reproduce the failure.
Puma exits immediately with a load error
Run bundle exec puma manually in /srv/myapp. A missing config.ru, an incorrect require path, an absent gem, or an unset production environment variable will appear directly in the terminal or journal. Fix that startup error before changing restart settings.
Nginx returns 502 Bad Gateway
A 502 normally means nginx cannot connect to the configured Puma endpoint. Confirm that systemd shows Puma as active, verify the listener with a local curl request, and compare nginx’s proxy_pass address with Puma’s bind address. For a Unix socket, check the socket path and permissions; for TCP, ensure the address is loopback and the port numbers match.
The browser shows the nginx default page
The request is probably matching the default server block or the hostname does not match server_name. Test sudo nginx -T to view the loaded configuration, disable the conflicting default site, and reload nginx. Confirm DNS sends the request to this VPS.
Certbot cannot complete HTTP-01 validation
Ensure the domain resolves publicly to this VPS, nginx is listening on port 80, UFW and any provider firewall allow port 80, and no other service intercepts the challenge path. Do not close port 80 until issuance and future renewal strategy are settled; HTTP-01 depends on it during validation.
Renewal succeeds but users still see an old certificate
Check which process terminates TLS. If it is nginx, confirm Certbot’s renewal hook or timer reloads nginx and inspect the active certificate after a renewal. If another service terminates TLS, configure that service’s reload hook rather than assuming the nginx plugin can reload it.
Deployment choices and their trade-offs
| Decision | Option A | Option B | When each fits |
|---|---|---|---|
| Process supervision | systemd on the VPS | Platform-managed service | Use systemd when you control the Ubuntu host; use a platform service when the provider owns process lifecycle and deployment. |
| Edge termination | nginx in front of Puma | Direct Puma exposure | nginx centralizes TLS, headers, and public ports. Direct Puma is simpler for a private test but exposes the app server and shifts TLS concerns to Puma. |
| Puma bind | Loopback TCP | Unix socket | Loopback TCP is easiest to diagnose. A socket is local by design but requires matching paths and permissions. |
| Ruby installation | Distribution package | Version manager | The package is operationally simple; a version manager is better when projects need different Ruby versions. |
| Release method | In-place update | Versioned releases and symlink | In-place is quick for a small service. Versioned releases make rollback and atomic cutovers easier. |
| Certificate automation | Certbot nginx plugin | Centralized ACME workflow | Certbot is convenient when nginx terminates TLS. A centralized workflow can suit estates with many hosts or a separate certificate controller. |
Operational habits after the first deployment
- Keep SSH access and the firewall configuration documented so an emergency reboot does not strand administrators.
- Review systemd and nginx logs after every release, especially when changing Ruby, Rack, Puma, or native gems.
- Restart Puma deliberately after deploying code; systemd does not know that files changed.
- Protect the environment file and rotate secrets through a controlled process rather than editing them in the Git checkout.
- Monitor disk space, memory, and certificate expiry. Puma worker and thread counts should be adjusted when resource usage changes.
- Practice a rollback: restore the previous release or commit, run Bundler for that version, and restart the service.
- Keep nginx and Ubuntu packages updated, but stage major Ruby or framework upgrades separately from routine security updates.
The Bottom Line
For an internet-facing Sinatra app, bind Puma privately, supervise it in the foreground with systemd, proxy through nginx, obtain HTTPS only after DNS and port 80 are correct, and allow only the ports the service actually needs. That sequence makes each layer testable and leaves you with a repeatable deployment rather than a manually launched Ruby process.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




