Most stores come to us from shared hosting. While orders are few, it’s tolerable. Then a sale starts, an ad campaign kicks in, or a competitor’s price scraper shows up — pages take ten seconds to load, the cart hangs, and the host emails you about “exceeding resource limits”.
The other common story is security. PrestaShop, WooCommerce and OpenCart all run on modules from many different authors, and an outdated module with a known hole is business as usual. Bots hammer /admin around the clock, probe for known vulnerabilities and try passwords. If a hundred other people’s sites share the same server, you control neither the PHP version nor the neighbours.
We move the store to its own server at OVH or Hetzner and build several layers of protection around it. We describe it here using PrestaShop, but WooCommerce and OpenCart follow the same pattern — only the configuration details differ.
What we build
We install Proxmox VE on the physical server and split the store into separate containers or VMs:
- Edge node — a reverse proxy (Nginx or Traefik), Let’s Encrypt certificates with automatic renewal, rate limiting and the WAF. It’s the only thing facing the internet.
- Web tier — Nginx + PHP-FPM at the version your store needs, OPcache for PHP code, Redis for sessions and the store cache.
- Database — MariaDB or MySQL in its own container, unreachable from outside, with buffers sized to your catalogue.
- Staging — a copy of the store with an anonymised database. This is where we update PrestaShop, modules and the theme before touching production.
- Monitoring — Prometheus and Grafana: load, 5xx errors, response times, disk space, certificate expiry. Alerts go to Telegram or email.
- Backups — daily database dumps and the media directory shipped to separate storage, plus container snapshots via Proxmox Backup Server.
The admin panel gets locked down separately: a renamed directory, access only from your IPs or over VPN, and two-factor authentication wherever the module supports it.
Layers of protection, from the network to PHP
DDoS protection works on several levels, and each one catches something different.
The provider. OVH and Hetzner filter volumetric attacks (UDP floods, SYN floods) on their own network before traffic reaches your server. It comes with the server, and for most stores that’s enough against the heavy stuff.
Cloudflare, when it makes sense. It hides the server’s real address, caches static files closer to shoppers and handles HTTP-level attacks well. But you don’t always need it: if your customers are in one country and nobody has attacked you, an extra middleman just makes debugging harder and adds another point of failure. If we do put Cloudflare in front, we close direct access to the server — otherwise attackers simply go around it. A neat option is Cloudflare Tunnel: the server connects outward on its own, and port 443 isn’t exposed to the internet at all.
Rate limiting. Application-level attacks usually target expensive pages: search, catalogue filters, cart, login. In Nginx we set separate limits for those routes:
limit_req_zone $binary_remote_addr zone=search:10m rate=2r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location ^~ /admin-xxxxxx/ { # admin directory
allow 198.51.100.0/24; # office or VPN
deny all;
}
location ~* /(search|module/.+/search) {
limit_req zone=search burst=10 nodelay;
}
WAF. ModSecurity with the OWASP Core Rule Set, or Coraza — it blocks SQL injection, XSS and attempts to read config files. This is what covers holes in modules you haven’t had time to update yet.
CrowdSec. It reads proxy and SSH logs and recognises behaviour: scanning, password guessing, known CVEs, aggressive crawlers. A bouncer at the edge enforces the bans. On top of that, CrowdSec shares a community blocklist: an IP that has already scanned thousands of other servers gets banned before its first request reaches you.
Where the WAF gets in the way, and how we handle it
To be honest: an out-of-the-box WAF will break things in a store. Payment and shipping modules suffer most. A callback from the payment gateway arrives with JSON or a signature that looks “suspicious” — and the order is stuck at “awaiting payment”. CSV product imports, the HTML description editor and marketplace APIs are other usual suspects for false positives.
So we don’t switch on blocking right away. For the first week the WAF runs in log-only mode. We look at what it catches on real traffic and write narrow exceptions: for a specific callback URL and a specific rule, not “disable the WAF for /module/”. Only then do we enable blocking. After every payment module update we run a test transaction on staging.
When this is overkill: if you get a few dozen orders a month and run on a hosted platform like Shopify, your own server will mostly give you extra things to worry about. Your own infrastructure pays off once the store has real volume, custom modules, or requirements about where its data lives.
What you get
- The store on a dedicated server, with separate containers for web, database and staging.
- Root access and every password — held by you, not just by us.
- A configured WAF and CrowdSec, with exceptions tuned to your payment and shipping modules.
- TLS with automatic certificate renewal.
- Daily backups of the database and media to separate storage, with restores that have actually been tested.
- A staging copy where updates can be tried safely.
- Grafana dashboards and alerts when something goes wrong.
- Documentation: what runs where, how to update it, how to restore from backup.
If your store has outgrown its hosting, or you’re tired of bots knocking on the admin panel, write to us. Tell us what the store runs on today and how much traffic it gets, and we’ll suggest how to move it.