Quick answer: To publish Phalcon on cloud hosting you need a server where you can install a PHP C extension — a VPS, a cloud instance, or a container. Provision the box, install PHP 8.3 or 8.4 with PHP-FPM, install the Phalcon extension (pie install phalcon/cphalcon, a distro package, or the official Docker image), point your web server’s document root at public/ with a rewrite to index.php, then upload your code, set permissions, and enable OPcache. Plain shared hosting will not work unless the host already ships Phalcon.
Key takeaways
- Phalcon is a compiled C extension, not a Composer package. Uploading your
vendor/folder does nothing — the extension has to exist in the PHP runtime itself. - That single fact rules out most cheap shared hosting and rules in VPS, cloud instances, and containers.
- Phalcon v5.17.0 (July 2026) requires PHP 8.1+, but you should deploy on PHP 8.3 or 8.4 for support reasons, not 8.1.
- The rewrite rule matters as much as the extension. A missing
try_filesline is the number one cause of “only the homepage works” after a deploy. - Docker is the least painful route if you deploy more than once, because the extension is baked into the image instead of installed by hand.
Why publishing Phalcon is different from publishing any other PHP app
Most PHP frameworks — Laravel, Symfony, CodeIgniter — are just PHP files. You upload them, run composer install, and they work anywhere PHP works. Phalcon is not that. It is written in Zephir and compiled down to a native C extension that PHP loads at startup. The framework lives in the interpreter, which is exactly why it is fast and exactly why deployment trips people up.
The practical consequence: you need permission to modify the PHP runtime on your server. That means root or sudo access, or a hosting provider that has already installed phalcon.so for you. If your host only gives you an FTP login and a control panel, you are almost certainly stuck.
Everything below assumes you have SSH access to a Linux box you control. If you do not have one yet, start with our roundup of the best VPS hosting services for 2026 — any provider on that list will do, because all you really need is a clean Ubuntu or Debian instance with root.
Requirements before you start
| Component | Minimum | Recommended (2026) | Notes |
|---|---|---|---|
| PHP | 8.1 | 8.4 | PHP 8.1 is end-of-life; 8.2 gets security fixes only until 31 Dec 2026 |
| Phalcon | 5.x | 5.17.0 | Released 17 July 2026 |
| Web server | Apache 2.4 | Nginx + PHP-FPM | Nginx uses less RAM per request on small instances |
| RAM | 1 GB | 2 GB+ | 4 GB is needed only if you compile the extension yourself |
| Access | sudo/root over SSH | sudo + a deploy user | Required to load a PHP extension |
| Composer | 2.x | 2.x | For your app’s PHP dependencies, not for Phalcon itself |
One note on PHP versions. The Phalcon docs still say “PHP 8.1 and above,” but php.net’s support table tells a harsher story: 8.1 is dead, 8.2 drops out of security support at the end of 2026, and 8.3 left active support at the end of 2025. Deploy on 8.4 unless something in your stack forces you lower. Shipping a new production app on an EOL runtime is how you end up patching under pressure later — see what happened when Microsoft shipped 570 fixes in a single Patch Tuesday.
Step 1: Pick the right kind of cloud hosting
“Cloud hosting” covers five very different products, and only some of them can run Phalcon at all.
| Hosting type | Can it run Phalcon? | Effort | Best for |
|---|---|---|---|
| Shared hosting | Only if the host pre-installs it | None or impossible | Hobby sites on a host that advertises Phalcon |
| VPS / cloud instance (DigitalOcean, Linode, Hetzner, EC2) | Yes | Medium — you install everything | Most production Phalcon apps |
| Managed PHP PaaS | Sometimes — check the extension list | Low | Teams that do not want to run servers |
| Containers (Docker, ECS, Kubernetes) | Yes | Medium up front, low afterwards | Anything you deploy more than once a month |
| Serverless (Lambda, Cloud Run functions) | Only with a custom layer or image | High | Advanced, spiky workloads |
For a first deployment, a $6–$12/month VPS is the honest answer. It gives you the root access Phalcon needs, and the pricing is predictable — worth mentioning in a year when the AI data center boom is squeezing hardware supply and pushing instance prices up across the board.
Step 2: Prepare the server
Spin up a clean Ubuntu 24.04 LTS instance, SSH in, and install the base stack. The Ondřej Surý PPA is what gives you current PHP builds and the Phalcon package.
# Update and add the PHP repository
sudo apt update && sudo apt upgrade -y
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
# Install Nginx, PHP 8.4 FPM and the extensions a typical Phalcon app needs
sudo apt install -y nginx php8.4-fpm php8.4-cli php8.4-mysql
php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip
php8.4-gd php8.4-intl php8.4-opcache unzip git
# Confirm
php -v
systemctl status php8.4-fpm --no-pagerCreate a dedicated non-root user for the application so your deploys never run as root:
sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG www-data deploy
sudo mkdir -p /var/www/myapp
sudo chown -R deploy:www-data /var/www/myappStep 3: Install the Phalcon extension
This is the step that makes the deployment “a Phalcon deployment.” Pick one of these four routes.
Option A: Distro package (fastest, recommended)
If you are on Ubuntu or Debian with the Ondřej PPA already added:
sudo apt install -y php-phalcon5
sudo systemctl restart php8.4-fpmOn RHEL, Rocky, or AlmaLinux with the Remi repository:
sudo dnf install -y pcre-devel
sudo dnf install -y php84-php-phalcon5
sudo systemctl restart php-fpmNo compilation, no 4 GB RAM requirement, and package upgrades come through your normal apt upgrade cycle. This is the right default for a production box.
Option B: PIE, the modern extension installer
PIE (PHP Installer for Extensions) is the Composer-style replacement for PECL, and it is now the method the Phalcon team points to first:
# Install PIE itself
curl -sSL https://github.com/php/pie/releases/latest/download/pie.phar -o pie.phar
sudo mv pie.phar /usr/local/bin/pie && sudo chmod +x /usr/local/bin/pie
# Install Phalcon
sudo pie install phalcon/cphalconHeads up: PIE compiles the extension, and the Phalcon docs warn you need at least 4 GB of RAM for the build. On a 1 GB droplet the compile will be killed by the OOM reaper. Either temporarily add swap, build on a bigger box, or use Option A.
Option C: PECL (deprecated but still works)
sudo pecl channel-update pecl.php.net
sudo pecl install phalconPECL is officially deprecated in favour of PIE. Use it only if you are automating against an older playbook you do not want to rewrite yet.
Option D: Build from source
Only necessary if you need an unreleased fix or a custom build:
sudo apt install -y php8.4-dev libpcre3-dev gcc make re2c
git clone https://github.com/phalcon/cphalcon
cd cphalcon
git checkout tags/v5.17.0
zephir fullclean
zephir buildEnable and verify
Package installs usually write the .ini file for you. If php -m does not list Phalcon, create the file yourself:
| OS / SAPI | Path for the ini file |
|---|---|
| Ubuntu/Debian, PHP-FPM | /etc/php/8.4/fpm/conf.d/30-phalcon.ini |
| Ubuntu/Debian, Apache | /etc/php/8.4/apache2/conf.d/30-phalcon.ini |
| Ubuntu/Debian, CLI | /etc/php/8.4/cli/conf.d/30-phalcon.ini |
| RHEL / CentOS / Rocky | /etc/php.d/50-phalcon.ini |
echo "extension=phalcon.so" | sudo tee /etc/php/8.4/fpm/conf.d/30-phalcon.ini
echo "extension=phalcon.so" | sudo tee /etc/php/8.4/cli/conf.d/30-phalcon.ini
sudo systemctl restart php8.4-fpm
# Verify — both should print the same version
php -m | grep -i phalcon
php -r 'echo (new PhalconSupportVersion())->get(), PHP_EOL;'Do not skip the CLI ini file. If you only enable the extension for FPM, your website will work but every migration, cron job, and CLI task will die with a “class not found” error.
Step 4: Upload your application
Deploy over Git rather than FTP. It is faster, atomic-ish, and gives you a rollback path.
sudo -u deploy -i
cd /var/www/myapp
git clone https://github.com/yourname/yourapp.git .
composer install --no-dev --optimize-autoloader --no-interactionA standard Phalcon project looks like this, and the only directory the web server should ever see is public/:
/var/www/myapp
├── app/
│ ├── config/
│ ├── controllers/
│ ├── models/
│ └── views/
├── cache/ ← must be writable
│ ├── volt/
│ └── metadata/
├── public/ ← document root
│ ├── index.php
│ ├── css/
│ └── js/
├── vendor/
└── .envNow fix ownership and permissions. Phalcon compiles Volt templates and caches model metadata at runtime, so those directories must be writable by the PHP-FPM user:
sudo chown -R deploy:www-data /var/www/myapp
sudo find /var/www/myapp -type d -exec chmod 755 {} ;
sudo find /var/www/myapp -type f -exec chmod 644 {} ;
sudo chmod -R 775 /var/www/myapp/cache
sudo chmod 640 /var/www/myapp/.envStep 5: Configure the web server
Nginx + PHP-FPM
Create /etc/nginx/sites-available/myapp. The try_files line is the critical part — it hands every unmatched URL to Phalcon’s router via the _url parameter:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/myapp/public;
index index.php;
charset utf-8;
client_max_body_size 20M;
location / {
try_files $uri $uri/ /index.php?_url=$uri&$args;
}
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_split_path_info ^(.+.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_hide_header X-Powered-By;
}
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires max;
access_log off;
log_not_found off;
}
location ~ /.(?!well-known) { deny all; }
}sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginxApache
If you are on Apache, enable mod_rewrite and use two .htaccess files. The one in the project root pushes everything into public/:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule ((?s).*) public/$1 [L]
</IfModule>And the one inside public/ routes everything to the front controller:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]
</IfModule>Your virtual host needs AllowOverride All for those files to be read at all:
<VirtualHost *:80>
ServerName example.com
DocumentRoot "/var/www/myapp/public"
DirectoryIndex index.php
<Directory "/var/www/myapp/public">
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>Step 6: Environment config and database
Never commit credentials. Keep them in .env outside version control and read them in your config:
APP_ENV=production
APP_DEBUG=false
DB_HOST=127.0.0.1
DB_NAME=myapp
DB_USER=myapp_user
DB_PASS=change-meA minimal production database service registration in your DI container:
<?php
use PhalconDbAdapterPdoMysql;
$di->setShared('db', function () {
return new Mysql([
'host' => getenv('DB_HOST'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'dbname' => getenv('DB_NAME'),
'charset' => 'utf8mb4',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
],
]);
});Then run your schema migrations with the official tool:
composer require --dev phalcon/migrations
vendor/bin/phalcon-migrations run --config=app/config/migrations.phpAnd make sure production error display is off — Phalcon will happily print stack traces containing your database password otherwise:
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/app-error.log
expose_php = OffStep 7: Turn on OPcache
Phalcon itself is compiled, but your application code is still interpreted PHP. Without OPcache you are throwing away a large part of the performance you chose Phalcon for. Add this to /etc/php/8.4/fpm/conf.d/10-opcache.ini:
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.save_comments=1
opcache.jit=tracing
opcache.jit_buffer_size=100M| Setting | Production value | Why |
|---|---|---|
validate_timestamps | 0 | Stops PHP stat-ing every file on every request. You must reload FPM after each deploy. |
memory_consumption | 128–256 MB | Too low and the cache thrashes; check opcache_get_status(). |
max_accelerated_files | 20000 | Must exceed your total PHP file count, vendor included. |
jit | tracing | Helps CPU-bound work; measure it, as gains on I/O-heavy web apps are small. |
Because validate_timestamps=0 means PHP ignores file changes, every deploy must end with sudo systemctl reload php8.4-fpm. Forget that and your “deployed” code will not actually be live.
Step 8: HTTPS, firewall, and hardening
# Free TLS certificate and auto-renewal
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Lock the firewall down to SSH and web traffic
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
# Unattended security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgradesAlso disable password SSH logins in /etc/ssh/sshd_config (PasswordAuthentication no) and keep the kernel patched. That last one is not boilerplate advice — the Dirty Frag flaw handed local attackers root across Ubuntu and RHEL this year, and attackers are moving faster than ever now that AI agents are running ransomware campaigns end to end. A public-facing cloud instance gets scanned within minutes of coming online.
Step 9: Deploy without downtime
Once the first deploy works, stop deploying with git pull in place. Use a releases directory and an atomic symlink swap so a half-finished composer install can never be served to users:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR=/var/www/myapp
RELEASE="$APP_DIR/releases/$(date +%Y%m%d%H%M%S)"
git clone --depth 1 git@github.com:yourname/yourapp.git "$RELEASE"
cd "$RELEASE"
composer install --no-dev --optimize-autoloader --no-interaction
# Shared, persistent state
ln -sfn "$APP_DIR/shared/.env" "$RELEASE/.env"
ln -sfn "$APP_DIR/shared/cache" "$RELEASE/cache"
# Atomic switch
ln -sfn "$RELEASE" "$APP_DIR/current"
sudo systemctl reload php8.4-fpm
# Keep the last five releases
ls -1dt "$APP_DIR"/releases/* | tail -n +6 | xargs -r rm -rfPoint your Nginx root at /var/www/myapp/current/public and rollback becomes a one-line symlink change.
Deploying Phalcon with Docker
If you deploy regularly, containers solve the hardest part of this guide: the extension ships inside the image, so “install Phalcon on the server” stops being a step at all. Phalcon publishes official images on Docker Hub and GHCR, tagged v[phalcon]-php[version]. There is deliberately no latest tag — pin your version.
FROM phalconphp/cphalcon:v5.17.0-php8.4
WORKDIR /srv/app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
RUN chown -R www-data:www-data /srv/app/cache
EXPOSE 9000The official image is FPM-only and intentionally ships without curl, git, composer, or xdebug to reduce attack surface, so pair it with an Nginx container:
services:
app:
build: .
restart: unless-stopped
volumes:
- ./:/srv/app
web:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./:/srv/app
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_DATABASE: myapp
MYSQL_ROOT_PASSWORD: change-me
volumes:
- dbdata:/var/lib/mysql
volumes:
dbdata:In the Nginx container config, fastcgi_pass app:9000; replaces the Unix socket path from Step 5. Everything else stays identical.
What about cPanel and shared hosting?
Some shared hosts do support Phalcon — a handful advertise it as a one-click PHP extension in cPanel, and a few PaaS providers let you toggle it from a dashboard. If yours does, enable it under Select PHP Version → Extensions and skip Step 3 entirely.
If it is not on the list, your realistic options are to open a support ticket and ask (some hosts will compile it for you on request), upgrade to that host’s VPS tier, or move. What you cannot do is install it yourself from a control panel without shell access. And if your provider offers a “PHP extension” upload field, be careful what binary you feed it — the same caution applies as when you scan any file before opening it.
Troubleshooting: the errors you will actually hit
| Symptom | Likely cause | Fix |
|---|---|---|
Class "PhalconMvcApplication" not found | Extension not loaded for that SAPI | php -m | grep phalcon for both CLI and FPM; add the missing ini file |
| Homepage works, every other route 404s | Missing or wrong rewrite rule | Check try_files ... /index.php?_url=$uri&$args; in Nginx, or AllowOverride All in Apache |
Volt directory can't be written | Cache dirs not writable by www-data | chown -R deploy:www-data cache && chmod -R 775 cache |
| White screen, nothing in the browser | display_errors=Off plus a fatal error | Read /var/log/php8.4-fpm.log and your Nginx error log |
| PHP won’t start after install | Extension built against a different PHP API version | Rebuild for the running PHP version, or use the matching distro package |
| Code changes don’t appear | opcache.validate_timestamps=0 | sudo systemctl reload php8.4-fpm after every deploy |
| 403 Forbidden on every URL | Document root points at the project root | Root must be the public/ directory, not the parent |
| Segfault under load | Extension conflict, often with Xdebug | Disable Xdebug in production; it should never be on a live server anyway |
Post-launch checklist
php -mshows Phalcon for both CLI and FPM- Every route resolves, not just
/ display_errorsis off and errors go to a log filecache/voltandcache/metadataare writable and populated after first load- OPcache is on with
validate_timestamps=0, and your deploy script reloads FPM - HTTPS works and certbot’s renewal timer is active (
systemctl list-timers | grep certbot) - UFW is enabled, SSH password auth is disabled
- Database backups are scheduled and you have restored one at least once
.envischmod 640and not reachable over HTTP
FAQ
Can I run Phalcon on shared hosting?
Only if the host already provides the extension. Phalcon is a compiled C module, so it must be installed at the PHP runtime level, which shared plans normally do not allow. Check your control panel’s extension list first, then ask support, then consider a VPS.
Do I still need Composer if Phalcon is an extension?
Yes — for your own dependencies and your PSR-4 autoloader. Composer just does not install Phalcon itself. The phalcon/cphalcon package on Packagist is the extension source, not a drop-in PHP library.
Which PHP version should I deploy on in 2026?
PHP 8.4. Phalcon 5.17 supports 8.1 and up, but 8.1 is end-of-life and 8.2 loses security support on 31 December 2026. PHP 8.3 works fine if a dependency blocks you from 8.4.
Why does my Phalcon site work locally but 404 in production?
Almost always the rewrite rule. Locally you were probably using php -S with a router script, which does not need one. In production, Nginx or Apache has to forward unmatched URLs to index.php, and the document root must be public/.
Is Docker better than installing on a VPS directly?
For a single site you will rarely touch, a plain VPS install is simpler. For anything with a real deploy cadence, multiple environments, or more than one developer, Docker wins because the extension version is pinned in the image instead of drifting on the server.
How do I upgrade Phalcon later?
If you installed via apt or dnf, it comes through normal package upgrades. With PIE or PECL, reinstall the extension and restart FPM. With Docker, bump the image tag and redeploy. Always test on a staging copy first — minor Phalcon releases occasionally change behaviour in models and forms.
The short version
Publishing Phalcon on cloud hosting is really one hard requirement wrapped in an ordinary PHP deployment. Get root on a cloud instance, install the extension with your package manager, point the document root at public/ with a rewrite to index.php, make cache/ writable, and switch on OPcache. Everything after that — TLS, firewall, atomic releases — is the same checklist you would run for any PHP app. And once it is working, containerise it so you never have to remember Step 3 again.

