CodeIgniter 4.7.5 on AWS User Guide
Overview
This guide covers launching and using the CodeIgniter 4.7.5 Host on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg app stacks and runtimes listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
CodeIgniter is a lightweight, fast PHP framework for building web applications and APIs. This image is a ready to use CodeIgniter host: the framework, PHP 8.3-FPM, nginx, MariaDB and Composer are installed and wired together, and a small sample application is already serving so you can see the whole stack working before you deploy your own code.
What the cloudimg image adds:
- CodeIgniter 4.7.5, current with the security fixes. 4.7.5 fixes six framework advisories that 4.7.4 still carries, including a critical upload validation bypass (GHSA-4hwf-v4mp-hf6c). The framework version is pinned exactly, its dependency tree is three permissive packages (MIT and BSD-3-Clause), and
composer auditwas clean at build time. - HTTPS only, from the first boot. nginx serves the application on port 443 with a certificate generated on your instance. There is no plain HTTP listener at all, and requests for a host name the instance does not serve are dropped.
- No default password. The admin password (HTTP Basic auth in front of every page except
/health), the MariaDB password the application uses and CodeIgniter'sencryption.keyare generated on first boot and written to/root/codeigniter-credentials.txt(root only). nginx and PHP-FPM refuse to start until first boot has finished. - Host and client address from configuration. CodeIgniter's
app.baseURLis set from the instance's configured name, and PHP is handed that configured name, never theHosta client sends, so every link and redirect names your site. Forwarding headers such asX-Forwarded-ForandX-Forwarded-Hostare removed before PHP sees them, so the client address is the real connection address. - Only the application on the web. nginx serves the
public/directory only and runs exactly one PHP file, CodeIgniter's front controllerpublic/index.php. Any other.phpURL answers 404, andapp/,vendor/,writable/and.envare outside the web root. The code is owned by root and read only to PHP; onlywritable/is writable. - Production mode.
CI_ENVIRONMENT=production: no debug toolbar, and errors never show a stack trace, a file path or SQL. - Hardened defaults. PHP-FPM and MariaDB listen on
127.0.0.1only. nginx, PHP-FPM, MariaDB and the first boot service are blocked from the instance metadata service, so PHP code (including anything that fetches a URL a user supplies) cannot read instance metadata. The AMI requires IMDSv2. - Your code and data on their own volume. The application, its
writable/directory and the MariaDB database live on a dedicated 20 GiB EBS volume at/srv/codeigniterthat you can grow independently of the root volume. - A development host. Composer stays on the image so you can
composer installyour own applications. There are no compilers or other build tools. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: CodeIgniter 4.7.5 (codeigniter4/appstarter and codeigniter4/framework), PHP 8.3 FPM with OPcache, nginx 1.24, MariaDB 10.11, Composer 2.7, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (the application) from your users. Nothing else is needed; never open 3306 or 9000.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick CodeIgniter 4.7.5 Host on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. The AMI brings two volumes: the 8 GiB root volume and the 20 GiB application volume; you can make either larger at launch.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=codeigniter}]'
First boot takes well under a minute after the instance is running.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| CodeIgniter 4.7.5 Host on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot creates the certificate, the passwords, the encryption key and the database, runs the application's migration, and then nginx and PHP-FPM start:
systemctl is-active codeigniter-firstboot nginx php8.3-fpm mariadb
sudo test -f /var/lib/cloudimg/codeigniter-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on network ports, nginx on 443 only. PHP-FPM (9000) and MariaDB (3306) listen on the loopback address:
sudo ss -Hltn | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
127.0.0.1:3306
127.0.0.1:9000
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
Step 4: Get your URL and passwords
sudo cat /root/codeigniter-credentials.txt
The file is readable by root only:
# CodeIgniter 4.7.5 Host - generated on first boot by codeigniter-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.
CI_APP_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
CI_ADMIN_USER=admin
CI_ADMIN_PASSWORD=<CI_ADMIN_PASSWORD>
CI_DB_NAME=codeigniter
CI_DB_USER=ci_app
CI_DB_PASSWORD=<CI_DB_PASSWORD>
CI_TLS_CERT=/etc/nginx/tls/codeigniter.crt
CI_CERT_SHA256=50:DE:DD:BE:...:BC:70:6A:67
CI_APP_DIR=/srv/codeigniter/app
#
# nginx (HTTPS on 443, HTTP Basic auth) is the only public entry point. PHP-FPM (127.0.0.1:9000) and MariaDB
# (127.0.0.1:3306) listen on loopback only. /health is an unauthenticated probe answered by nginx.
# The CodeIgniter encryption key is in /srv/codeigniter/app/.env.
# New admin password: sudo codeigniter-rotate-password
# Change the name it serves as: sudo codeigniter-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/codeigniter-4-7-5-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from the EC2 instance metadata. The certificate is generated on your instance and also names the public and private addresses:
openssl x509 -noout -subject -ext subjectAltName -enddate -in /etc/nginx/tls/codeigniter.crt
subject=CN = ec2-13-218-49-206.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-13-218-49-206.compute-1.amazonaws.com, IP Address:13.218.49.206, IP Address:172.31.92.245, IP Address:127.0.0.1, DNS:localhost
notAfter=Jan 22 05:38:38 2029 GMT
Step 5: Open the application
Open CI_APP_URL in your browser and sign in as admin with CI_ADMIN_PASSWORD. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own, Step 9). The sample application shows the CodeIgniter, PHP and MariaDB versions and a request counter:

The counter is a row in a MariaDB table that a CodeIgniter migration created on first boot; every request reads and writes it, so a number that goes up when you reload proves the migration ran and the database round trip works. The same facts are available as JSON for monitoring:

From the instance itself (the password is handed to curl on standard input, never on the command line):
H=$(sudo sed -n 's#^CI_APP_URL=https://\(.*\)/$#\1#p' /root/codeigniter-credentials.txt)
printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^CI_ADMIN_PASSWORD=//p' /root/codeigniter-credentials.txt)" \
| curl -s -K - --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" "https://$H/api/info" | jq .
{
"product": "cloudimg CodeIgniter 4 Host",
"codeigniterVersion": "4.7.5",
"phpVersion": "8.3.6",
"databaseServer": "10.11.14-MariaDB-0ubuntu0.24.04.1",
"dbConnected": true,
"environment": "production",
"baseUrl": "https://ec2-13-218-49-206.compute-1.amazonaws.com/",
"selfUrl": "https://ec2-13-218-49-206.compute-1.amazonaws.com/",
"requestHost": "ec2-13-218-49-206.compute-1.amazonaws.com",
"requestScheme": "https",
"clientIp": "127.0.0.1",
"requestsServed": 1
}
Without the password nothing is reachable except the health probe, which load balancers and monitoring can use:
H=$(sudo sed -n 's#^CI_APP_URL=https://\(.*\)/$#\1#p' /root/codeigniter-credentials.txt)
curl -s -o /dev/null -w 'anonymous -> HTTP %{http_code}\n' --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" "https://$H/"
curl -s --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" "https://$H/health"
anonymous -> HTTP 401
ok
Step 6: Add your own controller
The application lives in /srv/codeigniter/app (a standard CodeIgniter 4 app starter layout) and nginx serves /srv/codeigniter/app/public. Add a controller and a route exactly as on any CodeIgniter install. The code is owned by root, so edit it with sudo:
sudo tee /srv/codeigniter/app/app/Controllers/Hello.php >/dev/null <<'PHP'
<?php
namespace App\Controllers;
class Hello extends BaseController
{
public function index(): string
{
return 'Hello from my own CodeIgniter controller on ' . site_url('/');
}
}
PHP
sudo tee -a /srv/codeigniter/app/app/Config/Routes.php >/dev/null <<'PHP'
$routes->get('hello', 'Hello::index');
PHP
sudo php -l /srv/codeigniter/app/app/Controllers/Hello.php
sudo php -l /srv/codeigniter/app/app/Config/Routes.php
sudo systemctl reload php8.3-fpm
No syntax errors detected in /srv/codeigniter/app/app/Controllers/Hello.php
No syntax errors detected in /srv/codeigniter/app/app/Config/Routes.php
Use a quoted here-document (<<'PHP') so $routes reaches the file literally; php -l catches a broken Routes.php before you reload. Now open https://<your-instance>/hello in your browser, or fetch it over HTTPS from the instance:
H=$(sudo sed -n 's#^CI_APP_URL=https://\(.*\)/$#\1#p' /root/codeigniter-credentials.txt)
printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^CI_ADMIN_PASSWORD=//p' /root/codeigniter-credentials.txt)" \
| curl -s -K - --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" "https://$H/hello"; echo
Hello from my own CodeIgniter controller on https://ec2-13-218-49-206.compute-1.amazonaws.com/

Note that site_url() returns the configured https:// address: CodeIgniter builds every link from app.baseURL, never from the request.
Step 7: Deploy your own application
Replace the sample with your own code in /srv/codeigniter/app. Keep the existing .env: it holds this instance's database password and encryption key. Run Composer as root (the code is owned by root and read only to PHP), install without development packages, and reload PHP-FPM:
cd /srv/codeigniter/app
sudo COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader --no-interaction
sudo systemctl reload php8.3-fpm
Composer is part of the image:
composer --version
Composer version 2.7.1 2024-02-09 15:26:28
Keep writable/ owned by www-data and everything else owned by root. Your migrations go in app/Database/Migrations/ and run as the web user:
cd /srv/codeigniter/app && sudo -u www-data php spark migrate:status
CodeIgniter v4.7.5 Command Line Tool - Server Time: 2026-10-20 05:40:15 UTC+00:00
+-----------+-------------------+---------------------+---------+---------------------+-------+
| Namespace | Version | Filename | Group | Migrated On | Batch |
+-----------+-------------------+---------------------+---------+---------------------+-------+
| App | 2026-01-01-000000 | CreateStackCounters | default | 2026-10-20 05:38:42 | 1 |
+-----------+-------------------+---------------------+---------+---------------------+-------+
Apply new migrations with sudo -u www-data php spark migrate. The database is reachable from the instance as root over the local socket:
sudo mysql -t -e 'SELECT counter_key, hits FROM codeigniter.stack_counters;'
+-------------+------+
| counter_key | hits |
+-------------+------+
| requests | 1 |
+-------------+------+
Your application connects with the database.default.* settings already in .env (user ci_app on 127.0.0.1).
Step 8: Storage layout
findmnt -no SOURCE,TARGET,FSTYPE,SIZE,OPTIONS /srv/codeigniter
findmnt -no SOURCE,TARGET,FSTYPE /var/lib/mysql
/dev/nvme1n1 /srv/codeigniter ext4 19.5G rw,nosuid,nodev,relatime
/dev/nvme1n1[/mysql] /var/lib/mysql ext4
/srv/codeigniter is the application volume. /var/lib/mysql is a bind mount of /srv/codeigniter/mysql, so the database files live on the same volume while MariaDB keeps its standard path. Grow the volume in EC2 and then run sudo resize2fs /dev/nvme1n1.
Step 9: Your own DNS name and certificate
Point a DNS name at the instance (an Elastic IP keeps the address stable), then tell the instance to serve that name. This issues a new self signed certificate for it, pins nginx to it and sets CodeIgniter's app.baseURL:
sudo codeigniter-set-hostname <your-domain>
To use a certificate from a public CA, replace /etc/nginx/tls/codeigniter.crt and /etc/nginx/tls/codeigniter.key (keep the key 0600 root) and run sudo systemctl reload nginx. Requests for any other host name are dropped, and allowedHostnames stays empty, so CodeIgniter never builds a link from the Host a client sends.
Step 10: Security checks
A request for a host name the instance does not serve is closed without an answer:
curl -sk -o /dev/null -w 'forged Host -> HTTP %{http_code} (000 = connection closed)\n' -H 'Host: evil.example' https://127.0.0.1/health || true
forged Host -> HTTP 000 (000 = connection closed)
Forged forwarding headers change nothing: the application still builds its URLs from the configured name and sees the real client address:
H=$(sudo sed -n 's#^CI_APP_URL=https://\(.*\)/$#\1#p' /root/codeigniter-credentials.txt)
printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^CI_ADMIN_PASSWORD=//p' /root/codeigniter-credentials.txt)" \
| curl -s -K - --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" \
-H 'X-Forwarded-Host: evil.example' -H 'X-Forwarded-For: 203.0.113.9' "https://$H/api/info" | jq '{selfUrl, requestHost, clientIp}'
{
"selfUrl": "https://ec2-13-218-49-206.compute-1.amazonaws.com/",
"requestHost": "ec2-13-218-49-206.compute-1.amazonaws.com",
"clientIp": "127.0.0.1"
}
Only the front controller runs; the configuration and writable/ are not on the web, and there is no port 80 listener:
H=$(sudo sed -n 's#^CI_APP_URL=https://\(.*\)/$#\1#p' /root/codeigniter-credentials.txt)
for p in /.env /writable/ /app/Config/App.php /vendor/autoload.php; do
printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^CI_ADMIN_PASSWORD=//p' /root/codeigniter-credentials.txt)" \
| curl -s -o /dev/null -w "GET $p -> HTTP %{http_code}\n" -K - --cacert /etc/nginx/tls/codeigniter.crt --resolve "$H:443:127.0.0.1" "https://$H$p"
done
curl -s -o /dev/null -m 5 http://127.0.0.1/; echo "port 80 -> curl exit $? (7 = nothing listening)"
GET /.env -> HTTP 404
GET /writable/ -> HTTP 404
GET /app/Config/App.php -> HTTP 404
GET /vendor/autoload.php -> HTTP 404
port 80 -> curl exit 7 (7 = nothing listening)
The web stack is blocked from the instance metadata service (your own shell is not):
systemctl show -p IPAddressDeny nginx php8.3-fpm mariadb codeigniter-firstboot
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
The image also ships the check the build runs: sudo /usr/local/sbin/codeigniter-credential-round-trip.sh.
Step 11: Changing the admin password
sudo codeigniter-rotate-password
This writes a new random password for admin (as a bcrypt hash, never on a command line), records it in /root/codeigniter-credentials.txt and reloads nginx. To serve the application publicly without the password prompt (for example once your app has its own login), delete the two lines in /etc/nginx/codeigniter-auth.conf and run sudo systemctl reload nginx.
Step 12: Versions and licences
grep -E '^PINNED_' /opt/cloudimg/codeigniter-versions.txt
cd /srv/codeigniter/app && php -r '$l=json_decode(file_get_contents("composer.lock"),true); foreach($l["packages"] as $p) printf("%-26s %-8s %s\n",$p["name"],$p["version"],implode(",",(array)$p["license"]));'
PINNED_CODEIGNITER=4.7.5
PINNED_PHP=8.3.6
PINNED_MARIADB=10.11.14-MariaDB-0ubuntu0.24.04.1
PINNED_COMPOSER=2.7.1
codeigniter4/framework v4.7.5 MIT
laminas/laminas-escaper 2.18.0 BSD-3-Clause
psr/log 3.0.2 MIT
Troubleshooting
Nothing answers on 443. First boot has not finished or has failed; nginx and PHP-FPM stay down until it succeeds, so a failed first boot leaves the instance closed rather than open. Check systemctl status codeigniter-firstboot and sudo journalctl -u codeigniter-firstboot -n 40.
The browser says the connection was closed. You used a host name the instance does not serve. Use CI_APP_URL, or set your own name with codeigniter-set-hostname (Step 9).
A 500 error with no detail. That is production mode working as intended; the detail is in sudo tail /srv/codeigniter/app/writable/logs/log-$(date +%F).log.
The address changed after a stop and start. Without an Elastic IP the public name changes; run sudo codeigniter-set-hostname <new-name> with the new public DNS name.
Security recommendations
- Allow port 22 from your admin network only, and port 443 from your users. Never open 3306 or 9000.
- Use your own DNS name and a certificate your browsers trust (Step 9).
- Keep
CI_ENVIRONMENT = productionand keep the.envfile private: it holds the encryption key and the database password. - Keep the operating system patched, update CodeIgniter with Composer when new releases fix advisories, and snapshot the application volume regularly.
Support and licensing
CodeIgniter is open source under the MIT licence (Copyright British Columbia Institute of Technology and the CodeIgniter Foundation). The framework vendors Kint (MIT), PSR Log (MIT) and Laminas Escaper (BSD-3-Clause), and their licence files ship with the image. CodeIgniter is a trademark of the CodeIgniter Foundation; cloudimg is not affiliated with or endorsed by the CodeIgniter Foundation. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA