Hamdy Tabsissi
EN

This website In production Personal project

A complete system I run, with proof you can check live

The site you are reading runs on a server I set up, secure, update and monitor myself — from the bare machine to this page.

The need

A portfolio should prove skills, not just list them. Hosting it on a managed platform would hide exactly what I want to show: running a reliable, secure service over time.

The idea

Make the hosting itself the demonstration: everything written here can be checked live, on the very machine serving this page.

What I built

  • A cloud server in the Paris region, obtained after six attempts over several days — free capacity there is scarce.
  • The whole web stack, with each service running in its own restricted space so one problem can't spread to the others.
  • Hardening started on the first night — 7 measures applied in one night without any downtime — then continued step by step.
  • Automatic updates: an approved change goes live on its own, is health-checked, and rolls back automatically if the new version doesn't answer.
  • Continuous backups of the database, plus nightly snapshots kept daily, weekly and monthly.
  • A separate probe that checks the site's security every hour and publishes the result — you can see it below.

The result

  • 5security checks re-run every hour by a separate probe
  • 7applied in one night, zero downtime
  • 6attempts to get the server
  • 1 hbetween two independent security checks

It has served this portfolio since August 2026, alongside EventMap. The live checks below are measured by a separate probe, not written by hand.

Live checks

  • TLS verified
  • origin verified
  • headers verified
  • DNSSEC verified
  • uptime verified

These checks are measured every hour by a separate probe, through the site's public interface — not written by hand. Last run . The five checks, in detail (in French)

In pictures

Home page of this site: “I turn needs into useful systems”, on a starry background with a small orbiting system
Home page of this site: “I turn needs into useful systems”, on a starry background with a small orbiting system

What I learned

Never test only from the machine itself: with two firewalls stacked, a service can answer perfectly locally while nobody on the Internet can reach it. And choosing not to install something — here, a database server — is an operations skill too.

Technical details Stack, architecture and decisions — for technical readers

Stack

  • Ubuntu 22.04
  • Oracle Cloud ARM (Always Free)
  • nginx
  • Node.js + Express
  • SQLite (node:sqlite)
  • systemd
  • fail2ban
  • iptables
  • Cloudflare
  • GitHub Actions
  • Litestream + restic

Architecture at a glance

  1. Visitor's browser
  2. Cloudflare — TLS and cache
  3. nginx — security headers, rate limiting
  4. Node.js + Express — pages rendered on the server, in 7 languages; a sandboxed systemd service
  5. SQLite (node:sqlite) — projects and first-party visit records
  6. Around it: GitHub Actions tests every change; the VM pulls the validated release, checks /health and rolls back on failure; the Observatory re-checks security every 30 minutes.

How it works

  • VM with 4 OCPU / 24 GB (Ubuntu 22.04). nginx reverse proxy in front of isolated services, each under a dedicated system user without a shell, with ProtectSystem=strict and NoNewPrivileges.
  • First night (23 Aug 2026): admin password regenerated, fail2ban on SSH, strict security headers (CSP, HSTS, X-Frame-Options), last IP octet zeroed in access logs, bounded journald retention, nginx log rotation, encrypted backup of secrets stored off the VM.
  • Then: nginx rate limiting, rpcbind disabled, swap, remote desktop bound to 127.0.0.1 (SSH tunnel only), SSH password authentication disabled, Cloudflare Origin CA certificate in Full (strict) mode.
  • Pull-based continuous deployment: a push to master is validated by GitHub Actions, which moves a prod tag; the VM pulls it through a systemd timer, health-checks it and rolls back automatically. No inbound port, no VM credentials stored at GitHub.
  • Two stacked firewalls (iptables on the VM + the cloud Security List) — the reason I never test only from the machine itself.
  • Deliberate trade-off: security updates install automatically, but the VM never reboots or changes major version on its own.
  • First-party visit tracking only: no third-party cookie, IP addresses hashed before storage, records purged after 12 months.

Decisions & trade-offs

  • Pages rendered on the server rather than loaded in the browser: they stay indexable in seven languages and readable without JavaScript.
  • systemd services rather than Docker: production runs as sandboxed systemd units with CPU and memory quotas — a container that is never deployed would prove nothing.
  • One public JSON feed (/api/contenus.json), reused by the Mindmap, rather than a second API: one source of truth, nothing to keep in sync.

Incidents & fixes

  • After the redesign, the automatic pull-request report outgrew GitHub's 65,536-character comment limit (embedded screenshots): tests passed but publishing failed. Fixed with lighter thumbnails and a 60,000-character budget.
  • Cloudflare kept serving old scripts for up to 4 hours after a deployment: every CSS and JavaScript URL now carries a content fingerprint (?v=), so a new release is never mixed with old files.
  • In Arabic, a value such as “43 → 9.9 MB” was displayed backwards by the bidirectional text algorithm: Latin values are now isolated with Unicode marks.

Still missing

  • No automatic reboot — a deliberate choice