A private server for several websites on Docker and nginx
our own product: site isolation, HTTPS, self-hosted mail and backups
The task
Until 18 September 2026 the studio's websites were scattered across the VPN machines: the business card, the greeting-card site and a demo site on the Russian relay, client staging sites on the second Russian node, and the greeting-card service in Moldova. Every site edit risked taking the VPN down for all clients. The relay has 1.9 GB of memory and had already gone down once because of a neighbouring service. Certificates could not be issued for the second node, so one of the sites ran without HTTPS.
The studio needed a separate server where sites share neither memory, nor nginx, nor certificates with the VPN, and where each site is protected from its neighbours.
The solution
A dedicated studio web server in Moscow, whose configuration is kept in the lab-server repository. What is done:
- One entry nginx accepts HTTP and HTTPS, terminates TLS and routes requests to the sites.
- Each site runs in its own container and its own Docker network,
edge-<site>; sites cannot see one another. - All sites open over HTTPS and HTTP is redirected. A bare IP address returns nothing: the handshake is rejected and the connection is closed.
- Let's Encrypt certificates renew themselves, and nginx reloads its configuration after renewal.
- The entry nginx writes its logs to a file with rotation, and Docker logs are size-capped. The server and containers run in the Europe/Moscow time zone.
- Self-hosted mail for the studio's domain: receiving, sending and a web interface.
- Nightly snapshots of the mail and of the data of hosted sites, a watchdog for the VPN chain and a job that pulls a copy of the VPN database.
- Tunnels to services abroad, so the sites can reach bots and APIs without a direct connection.
How it works
One entry nginx for all sites
nginx is a web server that here acts as the single point of entry. It takes a request, looks at the site name and hands the request to the right site's container. The entry nginx configuration lives in a directory mounted into the container as a whole, so an edit is applied with a reload and the container does not have to be recreated. Unknown names are rejected.
Site isolation with Docker networks
A Docker network is a virtual network inside which containers see only each other. Every site gets its own edge-<site> network, and only the site's container and the entry nginx are on it. Site ports are not published to the outside. Databases are attached only to the internal networks of their own projects, so a break-in or failure in one site gives no path to a neighbour's database. A new site is added by one pattern: a network, a container, a name in the configuration, a certificate and a site file in conf.d.
HTTPS certificates: auto-renewal and nginx reload
Certificates are issued by certbot, with ownership checked through a webroot directory, so nginx never has to stop. Renewal is driven by the certbot.timer timer. A separate hook after each renewal makes nginx re-read the certificate: without it, nginx would keep serving the old one until it expired. A check script walks through all the sites and shows each certificate's remaining lifetime.
Self-hosted mail on the domain: docker-mailserver
A single mailserver container holds Postfix (sending and receiving), Dovecot (IMAP access), Rspamd (spam filtering) and fail2ban. The web interface is Roundcube. Antivirus is switched off: the ClamAV signature database takes more than a gigabyte out of four. Spam is not discarded but placed in the "Junk" folder, because at a small volume losing a client's message is worse than sorting a folder by hand. Mail to a non-existent address is rejected. A separate container forwards every new message to a service Telegram chat: subject, sender, text and attachments as files; spam arrives as a single line without a sound.
Tunnels to services abroad
Some services, including the Telegram bots, stay abroad, and from Russia new TCP connections to them often do not get through the filter. So connections are opened in advance and from abroad: ten independent frp clients in one balancing group carry the sites' requests to the service. If one session drops, new connections go to the other nine. The studio's lead bot (site requests into Telegram) reaches Telegram and an AI API through a separate egress tunnel.
Results
- Sites and the VPN are separated: editing a site can no longer take down the VPN.
- Every site is isolated in its own Docker network, and a bare IP address returns no content.
- Certificates renew automatically, and nginx picks them up without a restart.
- Domain mail is received on the studio's own server, and messages also arrive in Telegram.
- Nightly mail snapshots are kept for two weeks.
Technologies and why
- nginx — the entry point: TLS, request routing by site, logs.
- Docker Compose — a container and a Docker network for each site.
- certbot (Let's Encrypt) — free certificates with auto-renewal through webroot.
- docker-mailserver, Roundcube — self-hosted domain mail and its web interface.
- frp — tunnels through which the sites reach services abroad.
- Python — the studio's lead bot and the service that forwards mail to Telegram.
- logrotate, cron — log rotation and nightly snapshots.
Status
Version 1.16.0, dated 29 September 2026. The server has been in production since 18 September 2026, when the sites moved off the VPN servers. Since 29 September 2026 it has also run the studio's main site on Nuxt, alongside the business-card site of engineer Valery Afanasyev. Because the server lacks the memory to build Nuxt, the image is built on a workstation and loaded with docker load.
Open items per the README: outgoing mail is blocked by the hosting provider and queues up; some WordPress demo sites have no scheduled backups; the server sits with the same provider as the VPN nodes, so the sites are not decoupled from provider problems; the Telegram bots remain abroad and are reached through tunnels.
Questions about this project
How do I host several websites on one server so they do not interfere with each other?
Why a separate server for websites when VPN servers already exist?
How are HTTPS certificates renewed without manual work or downtime?
Can I run mail on my own domain instead of the hosting provider's?
Are backups kept?
What does not work as well as we would like yet?
More in this area
Peregovorka (ToshaStream): streaming server and team voice chat
our own product in production, version 1.50.0: voice, chats and a private stream
VPN infrastructure: Russian entry node, European exit
our own product: a relay in Russia, a persistent tunnel and a node in the Netherlands
Corporate gateway on Xray and nginx for an IT team
for an IT company: work tools available again, on its own servers
Need something similar?
Tell us about the task — we'll show how we solved it and estimate the scope.