Skip to main content

06 - Hosting low-cost: VPS Italia + scaling

A VPS for an indie product: what to check in the provider, a minimal nginx reverse proxy, nightly systemd backups and rotation checked by a program.

What do you think about this article?

06 - Hosting low-cost: VPS Italia + scaling
Indie Maker ItaliaPhoto:Daniil KomovPexels

At a Glance

A VPS for an indie product: what to check in the provider, a minimal nginx reverse proxy, nightly systemd backups and rotation checked by a program.

A single server, and the work it brings you

A managed cloud service takes the administrator's work off your hands and hands you the bill; a virtual server (VPS) costs little, but you are the administrator. For an indie product with a few customers, a VPS is often the right choice, provided you accept three tasks: keeping it updated, making backups and testing their restore. This article shows a minimal architecture with nginx, application and database, and two runnable tools: a backup rotation check and the systemd units that schedule the backups. On price: the Hetzner Cloud price list consulted on 2026-11-13 lists "from" plans at 5.49 euros per month for shared resources with regular performance, 5.99 euros for the cost-optimized plan and 36.99 euros for dedicated resources, all excluding VAT.

A clarification on the title of the series: I did not compare Italian providers and I do not report prices of operators I did not consult. The example uses a European provider with a public price list because that is the one I was able to verify; the method holds for any other.

What to look at when you choose the provider

The monthly price is the least informative item. Before choosing a provider, check on its page the points that follow; each has a precise reason.

  • Where the data is. For your customers' personal data the country of the data center matters, because Regulation (EU) 2016/679 governs transfers to third countries.
  • Price with and without VAT. B2B price lists are often VAT excluded, and if you cannot deduct the tax on purchases that part is a cost to you.
  • The provider's backups. Whether they exist, whether they are paid and where they are stored: they do not replace yours, but they are a second safety net.
  • Type of resources. "Shared" means the CPU is split with other customers; "dedicated" costs far more, as the price list shows.

The list comes from a single question: what happens if this server disappears tomorrow? Where the data is answers the legal part, backups the technical part, resources the performance part. VAT concerns only the books and, for those under the regime forfettario (Italy's flat-rate scheme for small sole traders), it is a flat cost: law 190/2014, paragraph (comma) 58, excludes the deduction for the transactions it lists, and for the rest of your expenses ask the accountant (commercialista). When comparing plans it pays to start from the smallest one that holds the estimated traffic, knowing that moving up to a larger one is a routine operation, while the opposite is more awkward.

The minimal architecture

The design is the same as in the first article: nginx accepts connections from outside, the application listens only on the local interface and the database accepts only the application. That way the only open port is the web server's, and the application cannot be reached directly from the Internet. A firewall completes the picture: the author uses ufw and fail2ban on the author's own server, but the general rule is simple, close everything and open only the web port and the administration one.

The block below is the smallest nginx configuration that does the job: a reverse proxy to the application. It is a scheme to adapt: nginx is not installed on the machine where I wrote the article, so the configuration was neither loaded nor tested, and www.example.com is a name reserved for examples. Before using it run nginx -t on your server, which checks the syntax without reloading.

server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;                 # the application listens only locally
        proxy_set_header Host $host;                       # without it, the app receives the proxy_pass host
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Output:
# scheme to adapt: nginx is not installed on this machine, the configuration was neither loaded nor tested

proxy_pass says where to forward. proxy_set_header Host $host restores the original host: without it, according to nginx's documentation, the default value is the host given in proxy_pass, and an application that builds absolute addresses (for example in emails) would write the wrong one. X-Forwarded-For and X-Forwarded-Proto tell the application the customer's address and whether the connection was encrypted. Port 80 here serves only as a starting point: the HTTPS certificate is obtained with a tool such as Certbot, whose site gives instructions for nginx, and it must be done before publishing the address.

Backups you can restore

A backup you have never restored is not a backup. PostgreSQL's command to export a database is pg_dump; with --format=custom it produces a compressed archive that is restored with pg_restore, and the custom format is described in the version 16 reference page as one of the most flexible, compressed by default. The options --format, --file and --dbname also appear in the help of pg_dump 17.10 installed on the working machine. I did not run it against any database, because the rule of this work forbids writing to local databases.

The rotation program that follows instead runs the part of the logic you can test on its own. The function ruota sorts the files by name, and since the name contains the date in ISO format the alphabetical order matches the chronological one; it keeps the last tieni files and deletes the others. The test runs in a temporary folder with ten fake files and a text file that must not be touched. The case tieni=0 deletes nothing: it is a protection against the wrong configuration that would empty the folder.

# Backup rotation: keep the last N files, delete the older ones.
# Test in a temporary folder with fake files: it touches no database.
import tempfile
from pathlib import Path

def ruota(cartella: Path, tieni: int):
    """Deletes the older backups. The name contains the ISO date, so the
    alphabetical order matches the chronological order. Returns the deleted files."""
    backup = sorted(cartella.glob("db-*.dump"))
    da_cancellare = backup[:-tieni] if tieni > 0 and len(backup) > tieni else []
    for f in da_cancellare:
        f.unlink()
    return [f.name for f in da_cancellare]

with tempfile.TemporaryDirectory() as tmp:
    cartella = Path(tmp)
    for giorno in range(1, 11):
        (cartella / f"db-2026-11-{giorno:02d}.dump").write_bytes(b"fake")
    (cartella / "note.txt").write_text("not a backup")   # must not be touched
    cancellati = ruota(cartella, tieni=7)
    print("deleted:", cancellati)
    print("remaining:", [f.name for f in sorted(cartella.iterdir())])
    print("tieni=0 deletes nothing:", ruota(cartella, tieni=0))

# Output:
# deleted: ['db-2026-11-01.dump', 'db-2026-11-02.dump', 'db-2026-11-03.dump']
# remaining: ['db-2026-11-04.dump', 'db-2026-11-05.dump', 'db-2026-11-06.dump', 'db-2026-11-07.dump', 'db-2026-11-08.dump', 'db-2026-11-09.dump', 'db-2026-11-10.dump', 'note.txt']
# tieni=0 deletes nothing: []

The result confirms the logic: ten files, seven remain, the three oldest are deleted and the text file stays intact. It is deterministic and identical across two runs. The limit is important: the program rotates files, it does not verify that they can be restored. The real verification is a manual procedure you put in your calendar: once a month you restore the latest backup on another machine or into a test database, and check that the data is there.

Scheduling the backup with systemd

On Linux the natural scheduler is systemd: a service-type unit that does the work and a timer-type unit that launches it. The two units below were checked with systemd-analyze verify, which reads the files without starting anything; I did not activate them, because they require a real database and a destination folder. The path /var/backups/mio-saas and the name mio_db are to be replaced.

# /etc/systemd/system/backup-db.service
[Unit]
Description=Backup notturno del database

[Service]
Type=oneshot
User=postgres
# %% is how you write a literal % in a unit file
ExecStart=/usr/bin/bash -c 'pg_dump --format=custom --file=/var/backups/mio-saas/db-$(date +%%F).dump mio_db'

# /etc/systemd/system/backup-db.timer
[Unit]
Description=Esegue il backup ogni notte

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target

# Output:
# systemd-analyze verify backup-db.service backup-db.timer: no message, exit 0

Type=oneshot tells systemd that the service finishes on its own. OnCalendar sets it at 03:30 every day, and Persistent=true makes it catch up a missed run if the server was off at that time, as the documentation describes. RandomizedDelaySec=10min shifts the start at random within ten minutes, useful if several servers of the same provider all start at the same hour. The file name contains the date, which is the format the rotation program expects. The backup still stays on the same server: copy it elsewhere, otherwise a disk failure takes data and copy away together.

When to scale, and how

The economical path is the vertical one: same server, bigger plan, a few minutes of downtime. A second server appears only when a single one is not enough, or when unavailability costs more than the complexity. Before scaling, measure: a single threshold written in advance, for example "CPU above 80% for a week", avoids decisions taken in a wave of panic. The price gap between the shared plans and the dedicated one in the price list, from 5.49 to 36.99 euros, gives you the order of magnitude of the jump.

Frequently asked questions

Is a VPS safe for a product with payments?

It depends on your work more than on the server. With a hosted checkout the card data does not pass through your server, and that greatly reduces the surface. Updates, firewall and SSH keys remain to be done, and those are your tasks.

How often should I make backups?

Whenever the lost data would hurt: for most small products, once a day. The example above keeps seven daily copies, a number you can change in one line.

Better a VPS or a managed service?

If you do not want to learn to administer a server, a managed service is an honest choice even if it costs more. The VPS pays off when cost is a variable and you know the tasks you are accepting.

What you can do now

You can evaluate a provider with four questions and a price list, write a minimal nginx reverse proxy, schedule a nightly backup with systemd and check the rotation logic with a program. You also know that the most important part is still missing, the tested restore. When customers arrive, support will show you where the service really breaks; you find the launch risks in the pre-mortem.

The traffic that comes from search engines is the subject of organic marketing, and how much traffic you really need you can estimate with the simulation in success stories.

Sources

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects