MISP (Malware Information Sharing Platform) is the de facto open-source standard for sharing threat intelligence indicators between organizations. It is what SOC teams, CERTs, and ISACs use to operationalize IOCs, attack patterns, and adversary tradecraft. This guide walks through a production deployment on a RamNode KVM VPS, including database tuning, worker configuration, hardening, and the operational concerns that the upstream installer skips over.
Sizing and Prerequisites
MISP is heavier than its reputation suggests. The web tier is PHP under FPM, the database is MariaDB, and there is a Redis-backed background worker pool that you cannot skip in production. Plan for the following minimums on a RamNode plan:
- 4 GB RAM minimum, 8 GB recommended once you start enabling feeds and correlation
- 4 vCPU minimum if you run correlation on imports
- 60 GB disk, ideally NVMe, since the database grows quickly with feed ingestion
- Ubuntu 24.04 LTS (24.04 is the cleanest path for the current installer)
RamNode does not allow mail services on their VPS, so the email notification features in MISP need to be configured against an external SMTP relay (SES, Postmark, Mailgun, or your own off-net mail server). Skip the local Postfix configuration steps that the upstream MISP docs include.
Initial Server Hardening
Start with a clean Ubuntu 24.04 install. Update and install the baseline tools:
apt update && apt -y full-upgrade
apt -y install ufw fail2ban unattended-upgrades curl gnupg2 software-properties-commonConfigure unattended security upgrades:
dpkg-reconfigure -plow unattended-upgradesLock down SSH. Edit /etc/ssh/sshd_config.d/99-hardening.conf:
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowUsers mispadmin
MaxAuthTries 3
LoginGraceTime 30Restart SSH and confirm key-based access works before closing the existing session:
systemctl restart sshConfigure UFW with a deny-by-default posture. We will open only what MISP needs:
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enableInstalling MISP
The upstream INSTALL.ubuntu2404.sh script is the supported path. Do not run it as root, do not run it interactively from a flaky SSH session, and do read it before executing. Pin to a tagged commit if you are deploying multiple servers from this base.
Create an unprivileged operator account first:
adduser mispadmin
usermod -aG sudo mispadmin
rsync -a --chown=mispadmin:mispadmin /root/.ssh /home/mispadmin/Switch to that user, then pull and run the installer:
su - mispadmin
wget --no-cache -O /tmp/INSTALL.sh https://raw.githubusercontent.com/MISP/MISP/2.5/INSTALL/INSTALL.ubuntu2404.sh
chmod +x /tmp/INSTALL.sh
sudo /tmp/INSTALL.sh -AThe -A flag runs the full default install: MariaDB, Apache, PHP 8.3, MISP core, MISP modules, and the background workers. Expect this to take 20 to 45 minutes depending on disk and network. The installer prints credentials at the end. Capture them into your password manager immediately and do not leave them in the shell history.
MariaDB Tuning for MISP Workloads
The default MariaDB config is sized for a generic LAMP host, not for MISP correlation. Edit /etc/mysql/mariadb.conf.d/99-misp.cnf with values that suit your plan size. The example below targets an 8 GB RamNode VPS:
[mysqld]
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4
innodb_log_file_size = 512M
innodb_log_buffer_size = 32M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_file_per_table = 1
max_connections = 200
table_open_cache = 4000
tmp_table_size = 256M
max_heap_table_size = 256M
query_cache_type = 0
query_cache_size = 0The buffer pool sizing rule is roughly 50 to 60 percent of RAM on a dedicated DB host, but since MISP shares the box with PHP-FPM and Redis, 4 GB out of 8 is the sane ceiling. On a 4 GB plan, drop this to 1.5 GB. Restart MariaDB and validate:
systemctl restart mariadb
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"TLS with Let's Encrypt
Replace the installer's self-signed certificate with a real one. Assuming your DNS A record for misp.example.org already points at the VPS:
apt install -y certbot python3-certbot-apache
certbot --apache -d misp.example.org --redirect --hsts \
--agree-tos --email admin@example.org --no-eff-emailForce HTTP/2 and add HSTS preload-grade headers. Edit /etc/apache2/sites-available/misp-ssl.conf inside the <VirtualHost> block:
Protocols h2 http/1.1
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder on
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305Reload Apache:
apache2ctl configtest && systemctl reload apache2MISP Core Configuration
Log into the web UI with the admin credentials from the installer output. The first task is to walk the diagnostics page at Administration > Server Settings > Diagnostics. Every red item is a real problem. Common items to fix on a fresh install:
MISP.baseurlshould be your full HTTPS URL, no trailing slashMISP.emailset to a real mailbox that you control (notifications only, RamNode will block 25/tcp)MISP.contactandMISP.orgset to your team identifiersSecurity.saltrotated if you cloned from another instanceGnuPG.emailand signing key generated for event signing
Generate a signing key:
sudo -u www-data gpg --homedir /var/www/MISP/.gnupg --quick-gen-key "MISP Signing <admin@example.org>" rsa4096 sign 0
sudo -u www-data gpg --homedir /var/www/MISP/.gnupg --export --armor admin@example.org > /var/www/MISP/app/webroot/gpg.asc
chown www-data:www-data /var/www/MISP/app/webroot/gpg.ascIn the UI, set GnuPG.binary to /usr/bin/gpg, GnuPG.homedir to /var/www/MISP/.gnupg, and the email to match the key.
External SMTP Configuration
Because RamNode blocks outbound mail traffic on standard ports for new accounts and prohibits running mail services on the VPS, MISP must relay through an external provider. In the server settings, set:
SMTP.hostto your provider hostname (for example,email-smtp.us-east-1.amazonaws.com)SMTP.portto 587SMTP.usernameandSMTP.passwordto your provider credentialsSMTP.transporttosmtpSMTP.tlsto true
Send a test notification from the diagnostics page and confirm delivery.
Background Workers
MISP relies on five worker queues: default, prio, email, cache, and update. The installer sets these up under systemd. Confirm they are running:
systemctl status misp-workersIf you are running with constrained RAM, edit /etc/systemd/system/misp-workers.service to cap the number of workers per queue. The default of 5 is excessive on a 4 GB plan; 2 per queue is usually sufficient until you scale up.
Monitor the queues from Administration > Jobs > Worker Status. Stuck workers are the single most common source of operational pain. Add this to your monitoring:
sudo -u www-data /var/www/MISP/app/Console/cake Admin getWorkersFeed Configuration
Feeds are where MISP earns its keep. Navigate to Sync Actions > List Feeds and enable the default CIRCL OSINT feed, the Botvrij feed, and any vendor or ISAC feeds you have access to. Set the caching schedule from the Administration > Scheduled Tasks page; pull and cache jobs should run on staggered intervals to avoid pinning the worker pool.
For high-volume feeds, disable correlation on import until you have validated the source. Correlation against a million-event corpus on a 4 vCPU VPS will pin the database for hours.
Backups
MISP backups need to capture three things: the database, the GnuPG keyring, and the app/files/certs and app/Config directories. Drop this into /usr/local/sbin/misp-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%Y%m%d-%H%M%S)
DEST=/var/backups/misp
mkdir -p "$DEST"
mysqldump --single-transaction --routines --triggers misp | gzip > "$DEST/misp-db-$TS.sql.gz"
tar -czf "$DEST/misp-config-$TS.tar.gz" \
/var/www/MISP/app/Config \
/var/www/MISP/app/files/certs \
/var/www/MISP/.gnupg
find "$DEST" -type f -mtime +14 -deleteSchedule it from root's crontab:
30 2 * * * /usr/local/sbin/misp-backup.sh >> /var/log/misp-backup.log 2>&1Push the backup directory off-box with rsync or restic to object storage. A backup that lives on the same VPS as the database is not a backup.
Operational Monitoring
Watch the following metrics, either via your monitoring stack or via cron-driven alerts:
- MariaDB connection count vs
max_connections - Redis memory usage and eviction rate
- Worker queue depth (alert if any queue is over 1000)
- Disk usage on
/var/lib/mysqland/var/www/MISP/app/files - Cert expiry on Let's Encrypt
- MISP UI HTTP 200 response from an external prober
Plausible Analytics or a self-hosted Uptime Kuma both work for the synthetic check.
What to Read Next
Once the platform is stable, the next round of work is integration: pushing IOCs into your SIEM, pulling events from MISP into a SOAR pipeline, and configuring sync to peer instances over the secure REST API. Each of those is a guide of its own, but a stable base instance with workers, backups, and TLS in good shape is the prerequisite for any of it.
