EspoCRM - Hardened Self-Hosted CRM
by Lynxroute
EspoCRM - CIS Level 1 hardened self-hosted CRM on Ubuntu 24.04 LTS with SBOM and CIS Report.
What is EspoCRM
EspoCRM is a mature open-source CRM (12+ years on the market) covering the full revenue pipeline - leads, accounts, contacts, opportunities, calendar, email integration, document storage, and reporting. Built on PHP 8 with PHP-FPM and MySQL 8.0, served by Nginx. The community edition is feature-complete for small-to-mid sales teams: multi-currency, role-based access control, custom entities and fields, formula scripting, workflow automation, and IMAP-aware email-to-record assignment.
Why self-host EspoCRM
Self-hosting puts every lead record, account note, and email thread inside your own Azure subscription - no per-seat SaaS fees and no third-party data residency questions. Suited to organisations with strict data residency requirements (GDPR, SOC 2, internal compliance), regulated industries that need an auditable on-premises sales platform, and teams moving from subscription CRM services to a self-hosted equivalent. AGPL-3.0 source - fully auditable, no vendor lock-in.
What this VM image adds
Security hardening:
- Admin password generated per instance - 20-character random password, stored in /root/espocrm-credentials.txt at first boot, never the same on two deployments
- Database password generated per instance - rotated at first boot, internal only, never written to user-facing files
- MySQL 8.0 listens on localhost only - no exposed database port; espocrm DB user limited to localhost
- PHP-FPM tuned at first boot - pm.max_children and OPcache memory sized from instance RAM
- Self-signed TLS at first boot - HTTPS on port 443 from launch; port 80 redirects to 443; replace with Let's Encrypt via Certbot for production
- Install wizard locked after first boot - /var/www/espocrm/install set to mode 0000 so the web installer cannot be re-triggered
- Trivy CVE scan - every image is scanned before release; CRITICAL/HIGH with available fix block the build
- UFW firewall - only ports 22, 80, and 443 open
- fail2ban - SSH brute-force protection
- AppArmor - mandatory access control on system services
OS hardening (CIS Level 1):
- CIS Level 1 hardened - CIS Ubuntu 24.04 LTS Level 1 Benchmark applied via ansible-lockdown
- auditd - system call auditing for critical paths
- SSH hardening - PasswordAuthentication disabled, key-only access, MaxAuthTries 4
- Kernel hardening - SYN cookies, ASLR, rp_filter, TCP BBR
- /tmp as tmpfs - nosuid, nodev, noexec
- Azure IMDS endpoints - egress rules pre-configured (169.254.169.254, 168.63.129.16)
Compliance artifacts (inside the VM):
- SBOM - CycloneDX 1.6 at /etc/lynxroute/sbom.json with SHA-256 hashes
- CIS Conformance Report - OpenSCAP HTML at /etc/lynxroute/cis-report.html, 0 FAIL rules on the tailored profile
- Tailored CIS profile - /usr/share/doc/lynxroute/CIS_TAILORED_PROFILE.md with documented exceptions
- Server credentials file - /root/espocrm-credentials.txt with the public IP, the web UI URL, and the per-instance admin password
Quick Start
- Deploy VM from Azure Marketplace (Standard_D2s_v3 or larger recommended)
- Open NSG: TCP 443 from your client networks - SSH 22 from your management IPs only
- SSH: ssh -i key.pem <username>@<PUBLIC_IP> (username set during VM creation, default: azureuser)
- Wait 60-90 seconds for first-boot setup; the page at https://<PUBLIC_IP>/ shows a "Starting up" splash during this window
- Read admin password: sudo cat /root/espocrm-credentials.txt
- Open https://<PUBLIC_IP>/, accept the self-signed cert warning, log in as "admin" with the generated password
- Change the admin password immediately (User Profile - Change Password)
- Issue a trusted HTTPS certificate (recommended for production): sudo apt install certbot python3-certbot-nginx -y && sudo certbot --nginx -d your.domain.com