Installation & Setup

Install Senddera on Rocky Linux 9

Rocky Linux 9 install — PHP 8.3 via Remi, MariaDB 11, Redis 7, nginx, certbot, supervisor — with dnf and SELinux notes.

May 9, 2026·8 min read· Intermediate

Rocky Linux 9 is the community-maintained binary-compatible RHEL 9 rebuild — the natural choice when your operations policy requires a Red Hat-family OS but you don't want to pay for a RHEL subscription. CentOS Stream and AlmaLinux work the same way; everything in this guide applies to all three with no command changes.

The hard differences from the Ubuntu and Debian guides: package manager is dnf (not apt), default firewall is firewalld (not ufw), SELinux is enforcing by default and will block the Senddera web user from writing logs/storage unless you set the right context, and PHP comes from the Remi repo (not Sury or Ondrej). Everything else — the workload sizing, the nginx vhost, the certbot flow, the supervisor config — is the same.

Step 0 — Pre-flight

Same as the Ubuntu pre-flight. Spec a 2 vCPU / 4 GB / 50 GB Rocky 9 droplet, point a DNS A record at it, create a sudo user, and have your Senddera-latest.zip + CodeCanyon code ready.

Step 1 — Base packages + EPEL

sudo dnf -y update
sudo dnf -y install epel-release
sudo dnf -y install curl wget unzip ca-certificates dnf-utils \
    policycoreutils-python-utils tar

policycoreutils-python-utils brings semanage, which we'll need to grant SELinux permissions. Without it, you can't add file contexts.

Step 2 — PHP 8.3 via Remi

The Remi repo is the canonical PHP source on RHEL-family distributions:

sudo dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
sudo dnf -y module reset php
sudo dnf -y module install php:remi-8.3
sudo dnf -y install php-fpm php-cli php-mysqlnd php-mbstring php-xml \
    php-curl php-zip php-gd php-intl php-imap php-gmp php-mailparse \
    php-bcmath php-redis php-soap

The module reset undoes RHEL's default PHP 8.0 module stream; the next line opts into Remi's 8.3 stream. Apply the Senddera-specific php.ini settings:

sudo sed -i 's/^memory_limit = .*/memory_limit = 512M/'              /etc/php.ini
sudo sed -i 's/^upload_max_filesize = .*/upload_max_filesize = 300M/' /etc/php.ini
sudo sed -i 's/^post_max_size = .*/post_max_size = 300M/'             /etc/php.ini
sudo sed -i 's/^max_execution_time = .*/max_execution_time = 300/'    /etc/php.ini

Note: RHEL has only one php.ini (vs. Ubuntu's separate fpm + cli). Set the FPM pool to run as nginx:

sudo sed -i 's/^user = apache/user = nginx/'   /etc/php-fpm.d/www.conf
sudo sed -i 's/^group = apache/group = nginx/' /etc/php-fpm.d/www.conf
sudo sed -i 's|^listen.owner = apache|listen.owner = nginx|' /etc/php-fpm.d/www.conf
sudo sed -i 's|^listen.group = apache|listen.group = nginx|' /etc/php-fpm.d/www.conf
sudo systemctl enable --now php-fpm

Step 3 — MariaDB 10.11

curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.11
sudo dnf -y install MariaDB-server MariaDB-client
sudo systemctl enable --now mariadb
sudo mysql_secure_installation
sudo mysql <<SQL
CREATE DATABASE Senddera
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'Senddera'@'localhost' IDENTIFIED BY '$(openssl rand -base64 24)';
GRANT ALL PRIVILEGES ON Senddera.* TO 'Senddera'@'localhost';
FLUSH PRIVILEGES;
SQL

Step 4 — Redis 7

sudo dnf module reset redis
sudo dnf module install redis:remi-7.2 -y
sudo systemctl enable --now redis

Step 5 — Nginx + firewalld

sudo dnf -y install nginx
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Vhost at /etc/nginx/conf.d/Senddera.conf (RHEL uses conf.d/, not sites-enabled/):

server {
    listen 80;
    server_name mail.example.com;
    root /var/www/Senddera/public;
    index index.php index.html;
    client_max_body_size 300M;

    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
    }
    location ~ /\.(?!well-known).* { deny all; }
}
sudo nginx -t && sudo systemctl reload nginx

Step 6 — Drop in the bundle (with SELinux)

This is where Rocky differs meaningfully from Ubuntu. Default SELinux lets nginx read files in /var/www, but not write them — and Senddera needs the web user to write to storage/ and bootstrap/cache/.

sudo mkdir -p /var/www/Senddera
cd /var/www/Senddera
unzip -q /path/to/Senddera-latest.zip -d .
sudo chown -R nginx:nginx /var/www/Senddera
sudo chmod -R 0755 /var/www/Senddera

# Mark storage + bootstrap/cache writable from web user under SELinux
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/Senddera/storage(/.*)?'
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/Senddera/bootstrap/cache(/.*)?'
sudo restorecon -R /var/www/Senddera/storage /var/www/Senddera/bootstrap/cache

# Allow nginx/php-fpm outbound network calls (sending API, license refresh)
sudo setsebool -P httpd_can_network_connect 1
sudo setsebool -P httpd_can_network_connect_db 1

Without httpd_can_network_connect=1, Senddera's Amazon SES driver and license verification calls will silently fail with permission errors that don't show up in any application log — only in /var/log/audit/audit.log. This is the single most-common Rocky/CentOS install gotcha.

Step 7 — TLS

sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d mail.example.com --non-interactive --agree-tos \
  --email you@example.com --redirect
sudo systemctl enable --now certbot-renew.timer

Step 8 — Supervisor

sudo dnf -y install supervisor
sudo tee /etc/supervisord.d/Senddera-worker.ini <<CONF
[program:Senddera-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/Senddera/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=nginx
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/Senddera-worker.log
CONF

sudo systemctl enable --now supervisord
sudo supervisorctl reread && sudo supervisorctl update
sudo supervisorctl start Senddera-worker:*

Step 9 — Cron

sudo crontab -u nginx -e
* * * * * cd /var/www/Senddera && php artisan schedule:run >> /dev/null 2>&1

Step 10 — Web installer

Browse to https://mail.example.com/install. Same flow as Ubuntu.

SELinux troubleshooting

If something doesn't work after install, the audit log is your friend:

sudo ausearch -m avc -ts recent | head -30
sudo ausearch -m avc -ts recent | audit2allow -a

Most-common AVCs on a fresh Senddera install:

Denial Fix
nginx … denied { write } for path=/var/www/Senddera/storage/... restorecon -R after semanage fcontext (Step 6)
php-fpm … denied { name_connect } for port=443 setsebool -P httpd_can_network_connect 1 (Step 6)
php-fpm … denied { name_connect } for port=3306 (remote DB) setsebool -P httpd_can_network_connect_db 1
php-fpm … denied { write } for path=/tmp/... chcon -t httpd_tmp_t /var/www/Senddera/storage/framework/cache

Related reading

FAQ

Can I disable SELinux instead of configuring it?

You can (setenforce 0 and SELINUX=permissive in /etc/selinux/config), but you shouldn't. SELinux materially reduces blast radius if Senddera or one of its dependencies has a remote-code vulnerability. The configuration above is the minimum confinement that lets Senddera work.

What about AlmaLinux / CentOS Stream / RHEL?

This guide works on all four with no command changes. Same package manager, same SELinux behavior, same firewalld.

Should I use the Software Collections (SCL) PHP?

No. Remi's modular streams are the actively-maintained option. SCL is on the path to deprecation in RHEL 10.

Why MariaDB and not MySQL?

Both work; MariaDB ships in EPEL and Remi, MySQL needs Oracle's repo. Senddera's MySQL adapter speaks both fine.

Migrating Rocky 8 → Rocky 9 with Senddera in place

Rocky 8 reaches end-of-life May 2029 (later than 9, paradoxically, due to the long RHEL 8 maintenance window) but new installs should start on 9 directly. If you have existing Senddera on Rocky 8 and want to move to 9, the safe path is:

  1. Snapshot the source droplet.
  2. Provision a fresh Rocky 9 droplet, run this install guide.
  3. Stop Senddera on Rocky 8 (supervisorctl stop Senddera-worker:* + crontab -r for the www-data user).
  4. mysqldump the database from Rocky 8, restore to the new Rocky 9.
  5. Rsync /var/www/Senddera/storage/ to the new droplet (preserves user-uploaded campaign assets).
  6. Update DNS to the new droplet's IP.
  7. Run php artisan migrate --force on the new droplet to apply any Senddera migrations that came with newer point releases.

In-place upgrade (Rocky's dnf system-upgrade or LEAPP for RHEL) is technically possible but risky for a multi-component stack like Senddera — if the upgrade leaves PHP modules in a half-upgraded state, debugging is hostile. The fresh-droplet approach is 30 minutes more work and dramatically safer.

Automated patching with dnf-automatic

Rocky 9 ships dnf-automatic for unattended security updates:

sudo dnf -y install dnf-automatic
sudo sed -i 's/^apply_updates = .*/apply_updates = yes/' /etc/dnf/automatic.conf
sudo sed -i 's/^upgrade_type = .*/upgrade_type = security/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timer

Set upgrade_type = security (not default) so you only auto-apply security errata — not feature updates that could change PHP/MySQL behavior unexpectedly. Pair this with the daily DB backup from the hardening checklist.

Run your email marketing on your own server

Full source code, no subscriber tax, unlimited sending. One-time $74 license, lifetime updates.

Get Senddera — $74 one-time