Krayin CRM 2.2.8
Overview
Krayin CRM is an open-source customer relationship management system built on Laravel by Webkul. It tracks leads through sales pipelines, keeps people, organisations, products and quotes, logs activities and e-mail against each lead, captures leads from web forms and notifies other systems through webhooks. Roles and permissions control what each user can see and do.
This guide covers the Krayin CRM 2.2.8 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud ERP and CRM listing on the AWS Marketplace, which you select as the software version when you subscribe and launch. The image is a hardened, ready-to-use Krayin CRM server.
What you get:
- The upstream release. Krayin CRM 2.2.8 from its GitHub tag, verified against its SHA-256. No Krayin source file is modified. Its PHP dependencies are installed from the tag's own lock file, unchanged (no known advisory), and its compiled front end ships as released.
SOURCE.txtlists exactly what is not shipped. - No default login. Krayin's sample administrator (
admin@example.com/admin123) never exists on this image. First boot creates one administrator,admin@sac.local, with a random password unique to your instance. APP_KEY (Laravel's encryption key) and the MySQL password are also generated on your instance. Until first boot has finished, neither nginx nor PHP starts, and Krayin's web installer is refused. - HTTPS from the first boot. nginx serves Krayin on 443 with a certificate generated on your instance (an end-entity certificate, not a certificate authority). Port 80 redirects to HTTPS. The server's name is set from the instance's EC2 public DNS name at first boot, and requests for any other name are dropped. You can change it to your own domain.
- MySQL 8.0 on the local socket only. The database server does not listen on any network port. Krayin connects over the Unix socket with its own user, which can reach only the
krayindatabase. The image ships an empty data directory: the database, its keys and its users are created on your instance. - Hardened web tier. PHP runs as its own unprivileged user on a Unix socket only nginx can reach, with every way of starting a program disabled. nginx passes Krayin the real client address from the connection, never a header a client sends, limits sign-in and password-reset attempts per address (Krayin also locks an e-mail address and client address out after five failed attempts), refuses cross-site requests to the data-import steps and the dashboard PDF export, and refuses the front controller path, the web installer, configuration, logs and the source tree. Debug mode is off and the environment is
production. - Outbound integrations off until you configure them. Mail goes to Krayin's log until you set an SMTP server, inbound e-mail answers "not configured" until you set a token or an IMAP account, and no web form, webhook or AI key exists. No credential for any outside service is on the image.
- Egress backstop. The
krayinuser (the web application, the queue worker, the scheduler and every command run throughkrayin-artisan) cannot reach the instance metadata service, link-local, reserved or multicast addresses, any IPv6 address, or any private address inside your VPC. A webhook URL, an IMAP server or a mail server typed into Krayin therefore cannot be used to probe your network. You can allow specific in-VPC hosts (Step 12). - Metadata service blocked. nginx, PHP, MySQL, the queue worker, the scheduler and the first-boot service cannot reach the EC2 instance metadata service. The AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Krayin CRM 2.2.8 (MIT), PHP 8.3 FPM and CLI, MySQL 8.0, nginx, nftables, cloud-init and the AWS Systems Manager agent, all from Ubuntu 24.04 except Krayin. Node.js and Composer are not on the image. Four PHP packages come from Ubuntu's universe component: php8.3-fpm, php8.3-intl, php8.3-zip and php8.3-bcmath; they are listed in /var/lib/sac/data/krayin-universe-packages.txt.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large (2 vCPUs, 8 GiB). A t3.large is enough for a small team.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 22 (SSH) from your own networks only, TCP 443 from wherever your users are, and TCP 80 (redirects to HTTPS). No other port is needed.
- A DNS name you control (for example
crm.example.com) and, ideally, a certificate from a certificate authority. Until you set these up, the instance uses its EC2 public DNS name with its own self-signed certificate.
Connecting to your instance
Operating system and SSH user:
Ubuntu 24.04 ubuntu
Launch the instance
Launch from the AWS Marketplace page and choose the Krayin CRM 2.2.8 on Ubuntu 24.04 version. Alternatively, launch with the AWS CLI, using the AMI ID shown on the launch page of the listing:
aws ec2 run-instances --image-id ami-xxxxxxxxxxxxxxxxx --instance-type m5.large \
--key-name my-key --security-group-ids sg-xxxxxxxx --subnet-id subnet-xxxxxxxx \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=krayin-crm}]'First boot takes about a minute once the instance is up. It initialises MySQL, creates the database, runs the Krayin migrations and seeders (pipelines, stages, attributes, roles, e-mail templates), and creates the administrator and the certificate. The web interface stays offline until first boot has finished.
Connect to your instance
ssh -i my-key.pem ubuntu@<public-ip>Check the services
systemctl is-active mysql.service nginx.service php8.3-fpm.service krayin-queue.service krayin-egress-guard.service
sudo ss -ltnp | awk 'NR==1 || /:(22|80|443|3306|33060) /'
sudo ss -lxp | grep krayin-fpmactive active active active active State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=72867,fd=7),("nginx",pid=72866,fd=7),("nginx",pid=69663,fd=7)) LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=72867,fd=5),("nginx",pid=72866,fd=5),("nginx",pid=69663,fd=5)) LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=898,fd=3),("systemd",pid=1,fd=88)) LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=72867,fd=8),("nginx",pid=72866,fd=8),("nginx",pid=69663,fd=8)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=72867,fd=6),("nginx",pid=72866,fd=6),("nginx",pid=69663,fd=6)) LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=898,fd=4),("systemd",pid=1,fd=89)) u_str LISTEN 0 4096 /run/php/krayin-fpm.sock 151018 * 0 users:(("php-fpm8.3",pid=69659,fd=10),("php-fpm8.3",pid=69658,fd=10),("php-fpm8.3",pid=69653,fd=8))Only SSH, HTTP and HTTPS listen on the network. MySQL (3306, and its X protocol on 33060) does not listen at all, and PHP listens only on its Unix socket.
Get your address and the administrator password
First boot writes everything you need to sign in to a root-only file:
sudo cat /root/krayin-credentials.txt# Krayin CRM 2.2.8 - generated on first boot by krayin-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret. KRAYIN_URL=https://ec2-3-82-139-119.compute-1.amazonaws.com/admin/login KRAYIN_ADMIN_EMAIL=admin@sac.local KRAYIN_ADMIN_PASSWORD=<24 random letters and digits> KRAYIN_TLS_SHA256=<the certificate's SHA-256 fingerprint> ...- KRAYIN_URL is your Krayin sign-in page.
- KRAYIN_TLS_SHA256 is the fingerprint of the instance's own certificate. Your browser warns that the certificate is not trusted. Compare the fingerprint it shows with this one before you continue. When you can, replace the certificate with one from a certificate authority for your own name (Step 13).
- KRAYIN_ADMIN_EMAIL / KRAYIN_ADMIN_PASSWORD sign in to Krayin as its administrator.
Run the self-test
The image includes an end-to-end test. Over HTTPS on the instance, it checks that:
- port 80 redirects
- the front controller path, the web installer, the configuration file, the data and the sources are refused
- the health check answers
- the administrator signs in through the Krayin sign-in form
- a wrong password and common default passwords are refused
Run the test:
sudo krayin-cred-roundtripKRAYIN_ROUNDTRIP_OK https 301 + no index.php/installer/env/data/sources + healthz + administrator sign-in (Krayin's own sign-in form, CSRF token) + wrong and default passwords refusedSign in to the web interface
Open KRAYIN_URL in your browser, check the certificate fingerprint, and sign in with KRAYIN_ADMIN_EMAIL and KRAYIN_ADMIN_PASSWORD.
Change the administrator's name and e-mail address under Settings > Users, or under My Account from the avatar at the top right.
To change the password, run one of the following commands on the instance. The first generates a new random password. The second reads your own password (20 to 72 letters and digits) from the terminal, never from the command line. Either way, every session is signed out and the new password is written to /root/krayin-credentials.txt:
sudo krayin-set-password# Type <new-password> at the prompt; nothing is echoed. read -rs -p 'New Krayin password: ' P; echo printf '%s\n' "$P" | sudo krayin-set-password --stdin; unset PAfter you store the password somewhere safe, you can delete the KRAYIN_ADMIN_PASSWORD line from /root/krayin-credentials.txt.
Work with leads, contacts and quotes
After you sign in, you land on the Dashboard. It summarises won and lost revenue, leads, quotes, people and organisations for a date range.
Leads shows the Default Pipeline as a board with the stages New, Follow Up, Prospect, Negotiation, Won and Lost. Select Create Lead to add a lead with its contact person, value, source and products. Drag a card between stages as the lead progresses, or switch to the list view with the icon at the top right.
Contacts holds persons and organisations, Products holds your catalogue, Quotes holds quotations linked to leads, and Activities holds calls, meetings and notes.
Under Settings, you can add users, roles and groups, edit pipelines and stages, and define custom attributes, sources, types and tags. You can also create Web Forms (lead-capture forms for your website) and Webhooks. A new instance has no web forms or webhook URLs until you add them.
The queue worker and the scheduler
Krayin sends e-mail and runs other background work through a queue. On this image, the queue worker is the krayin-queue service, which runs as the unprivileged krayin user:
systemctl show -p User -p ActiveState -p SubState krayin-queue.service
sudo krayin-artisan schedule:list
systemctl show -p UnitFileState krayin-schedule.timerUser=krayin ActiveState=active SubState=running 0 0 * * * php artisan campaign:process ...... Next Due: 14 hours from now */5 * * * * php artisan inbound-emails:process Next Due: 52 seconds from now UnitFileState=disabledThe Krayin scheduler has two tasks. inbound-emails:process fetches mail from an IMAP account every five minutes, and campaign:process sends marketing campaigns once a day.
Both tasks need mail that you configure (Steps 10 and 11), so the scheduler timer is installed but switched off on a new instance. Switch it on after you set up mail:
sudo systemctl enable --now krayin-schedule.timerThe Krayin command line
krayin-artisan runs the Krayin artisan commands as the krayin user, with the same metadata block and egress backstop as the web tier:
sudo krayin-artisan --version sudo krayin-artisan about --only=environmentLaravel Framework 12.71.0 Environment ................................................................ Application Name ................................................ Krayin CRM Laravel Version .................................................... 12.71.0 PHP Version .......................................................... 8.3.6 Composer Version ......................................................... - Environment ..................................................... production Debug Mode ............................................................. OFF URL ............................... ec2-3-82-139-119.compute-1.amazonaws.com Maintenance Mode ....................................................... OFF Timezone ............................................................... UTC Locale .................................................................. enSend e-mail
Out of the box, Krayin writes outgoing mail (password resets, e-mail to leads, campaigns) to its log instead of sending it. To send through your mail server or a service such as Amazon SES, edit /etc/krayin/env as root and set the mail values:
MAIL_MAILER=smtp MAIL_HOST=email-smtp.us-east-1.amazonaws.com MAIL_PORT=587 MAIL_ENCRYPTION=tls MAIL_USERNAME=<your SMTP user name> MAIL_PASSWORD=<your SMTP password> MAIL_FROM_ADDRESS=crm@example.com MAIL_FROM_NAME="Krayin CRM"Then clear Krayin's cached configuration and restart PHP and the queue worker:
sudo krayin-artisan config:clear
sudo systemctl restart php8.3-fpm krayin-queueOnly root and the krayin group can read the file. If your mail server has a private address inside your VPC, allow it first (Step 12).
Receive e-mail
Krayin can file incoming e-mail against leads and contacts in two ways.
- IMAP. Set
MAIL_RECEIVER_DRIVER=webklex-imapin/etc/krayin/env, runsudo krayin-artisan config:clear, enter the mail account under Configuration > Email > IMAP Settings, then switch on the scheduler (Step 8), which fetches new mail every five minutes.sudo krayin-artisan inbound-emails:processfetches it once by hand. - SendGrid Inbound Parse. Leave
MAIL_RECEIVER_DRIVER=sendgrid, setMAIL_RECEIVER_INBOUND_TOKENin/etc/krayin/envto a long random value (for example the output ofopenssl rand -hex 32), runsudo krayin-artisan config:clearandsudo systemctl restart php8.3-fpm, and point SendGrid's Inbound Parse webhook athttps://<your-domain>/admin/mail/inbound-parsewith the token in anX-Inbound-Tokenheader. Until a token is set the webhook answers503 Inbound parsing is not configured, so nobody can inject mail.
Allow hosts inside your VPC
The egress backstop stops the krayin user (the web application, the queue worker, the scheduler and krayin-artisan) from connecting to any address in your VPC's own address ranges, which it reads from the instance metadata at every boot. Public addresses work as usual. The following commands show the ranges, and that Krayin is refused the metadata service while public sites are not refused:
sudo nft list set inet sac_krayin_egress vpc4 | grep elements
sudo -u krayin curl -s -m 5 -o /dev/null -w 'metadata: %{http_code}\n' http://169.254.169.254/latest/meta-data/ || true
sudo -u krayin curl -s -m 5 -o /dev/null -w 'public HTTPS: %{http_code}\n' https://secureanycloud.com/ || trueelements = { 172.31.0.0/16 } metadata: 000 public HTTPS: 200If Krayin must reach a host inside the VPC (an SMTP relay, an IMAP server, a webhook receiver or a VPC interface endpoint on a private address), add its address or CIDR, one per line, to /etc/sac/krayin-egress-allow.conf and apply it. This restarts PHP and the queue worker with the new rules:
echo '172.31.10.25' | sudo tee -a /etc/sac/krayin-egress-allow.conf >/dev/null
sudo systemctl restart krayin-egress-guard
sudo nft list set inet sac_krayin_egress allow4 | grep elementselements = { 172.31.10.25 }Only the addresses you list are opened. The metadata service, link-local, reserved and multicast ranges and IPv6 stay refused whatever the file says.
Use your own domain and certificate
Point a DNS name at the instance. An Elastic IP is recommended. Then run:
sudo krayin-set-hostname <your-domain>This command creates a new self-signed certificate for the name and pins nginx to it, so other names are dropped. It also sets the Krayin APP_URL (used in links in its e-mails and web forms) and the cross-site guard, and updates the URL and fingerprint in /root/krayin-credentials.txt. To use a certificate from a certificate authority instead, replace /etc/nginx/ssl/krayin.crt and krayin.key (keep the key 0600 root) and run sudo systemctl reload nginx.
Restrict access to your networks
Limit who can reach the web interface in the security group of the instance. For example, to allow HTTPS only from your office network, run:
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 revoke-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 443 --cidr 0.0.0.0/0Krayin locks an e-mail address and client address out of sign-in after five failed attempts, and locks a client address after twenty. nginx separately rate-limits requests for the sign-in and password-reset pages per client address.
Check the security posture
U=$(sudo sed -n 's/^KRAYIN_URL=//p' /root/krayin-credentials.txt); U=${U%/admin/login} C=/etc/nginx/ssl/krayin.crt for p in admin/login healthz up; do curl -s --cacert "$C" -o /dev/null -w "GET /$p: %{http_code}\n" "${U}/$p" done for p in index.php index.php/admin/login .env install _debugbar/open storage/logs/laravel.log; do curl -s --cacert "$C" -o /dev/null -w "GET /$p: %{http_code}\n" "${U}/$p" done curl -s --cacert "$C" -o /dev/null -w 'GET /admin/dashboard/export-pdf from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "${U}/admin/dashboard/export-pdf" curl -s -o /dev/null -w 'GET http://: %{http_code}\n' "${U/https:/http:}/admin/login" curl -sk -o /dev/null -w 'GET with an unknown Host: %{http_code}\n' -H 'Host: not-this-server.example' https://127.0.0.1/ || true for i in $(seq 1 12); do curl -s --cacert "$C" -o /dev/null -w '%{http_code} ' "${U}/admin/login"; done; echo systemctl show -p IPAddressDeny nginx.service php8.3-fpm.service mysql.service krayin-queue.service
sudo mysql -e "SELECT @@version, @@bind_address, @@skip_networking, @@local_infile" curl -s http://127.0.0.1/healthzGET /admin/login: 200 GET /healthz: 200 GET /up: 200 GET /index.php: 404 GET /index.php/admin/login: 404 GET /.env: 404 GET /install: 404 GET /_debugbar/open: 404 GET /storage/logs/laravel.log: 404 GET /admin/dashboard/export-pdf from another site: 403 GET http://: 301 GET with an unknown Host: 000 200 200 200 200 200 200 200 200 200 200 429 429 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 @@version @@bind_address @@skip_networking @@local_infile 8.0.46-0ubuntu0.24.04.4 127.0.0.1 1 0 ok- The front controller path (
/index.php/...), the configuration file, the web installer, logs and the debug bar are refused. Krayin's code, configuration and data live outside the web root, so any other path that is not a Krayin page gets Krayin's own "404 Page Not Found" page. A link from another site cannot trigger the PDF export or a data import (403). Requests for any name other than the configured one are dropped (000: nginx closes the connection). - The twelve quick requests for the sign-in page show the nginx per-address limit (
429). - IPAddressDeny is the metadata-service block on nginx, PHP, MySQL and the queue worker; the scheduler and the first-boot service have the same one.
- MySQL has networking switched off (
skip_networking1) and refusesLOAD DATA LOCAL. http://127.0.0.1/healthzreturnsokfor a monitoring agent on the instance.
Back up your data
Your data is the MySQL database plus uploaded files in /var/lib/krayin/storage/app and /var/lib/krayin-public. Keep /etc/krayin/env too: its APP_KEY decrypts the values Krayin stores encrypted, and a backup restored without it cannot read them. Copy them to root-only files, and keep copies off the instance, for example in an encrypted S3 bucket:
sudo sh -c 'umask 077; mysqldump --single-transaction --routines --triggers krayin | gzip > /var/backups/krayin-db-$(date +%F).sql.gz'
sudo sh -c 'umask 077; tar -C /var/lib -czf /var/backups/krayin-files-$(date +%F).tar.gz krayin/storage/app krayin-public'
sudo sh -c 'umask 077; cp /etc/krayin/env /var/backups/krayin-env-$(date +%F)'
sudo ls /var/backups/ | grep krayinkrayin-db-2026-11-08.sql.gz krayin-env-2026-11-08 krayin-files-2026-11-08.tar.gzTo restore into a new instance of the same version, stop nginx, PHP-FPM and krayin-queue (and krayin-schedule.timer if you switched it on), load the dump into the krayin database with gunzip -c <dump> | sudo mysql krayin, unpack the files archive into /var/lib, put your APP_KEY line into /etc/krayin/env, and start the services again.
EBS snapshots of the instance are a simple way to keep point-in-time copies of the whole server.
Updates
Ubuntu, PHP and MySQL security updates install automatically (unattended upgrades). New Krayin releases are published as new versions of this listing: launch the new version, stop its services, copy your database, uploads and the APP_KEY line of /etc/krayin/env across (keeping the new instance's own database password and APP_URL), then run sudo krayin-artisan migrate --force and start the services again.
Licences and source
ls /usr/share/doc/krayin/ sed -n '1,6p' /usr/share/doc/krayin/SOURCE.txtLICENSE SOURCE.txt Krayin CRM 2.2.8 on Ubuntu 24.04 by sac - source and changes Upstream: the Krayin CRM 2.2.8 tag archive, https://github.com/krayin/laravel-crm/archive/refs/tags/v2.2.8.tar.gz (sha256 beb916c4a73ec7a7acc5f08e9192b0a0687ad28940e221a781484603d1de0fcc; GitHub tag v2.2.8, commit ed6deebfcb8c616eaf352c893a10a6d8e666fc6e). Licence: MIT (LICENSE, shipped here and in /var/www/krayin). No Krayin source file is modified.Krayin CRM is licensed under the MIT License by Webkul, and every Krayin package on the image is part of the same open source repository; the image contains no paid or proprietary extension. SOURCE.txt names the exact upstream archive and the files that are not shipped (tests, sample configuration, agent and lint tooling, front-end build configuration, Apache and IIS files, and one library README that carries example keys).
Security notes
- Secrets. The administrator password is in
/root/krayin-credentials.txtuntil you remove it; Krayin stores only its bcrypt hash. APP_KEY and the MySQL password are in/etc/krayin/env, readable by root and thekrayingroup only (not by the web server). The TLS key is in/etc/nginx/ssl/krayin.key(root only). No secret is on any command line, in a systemd unit or in a log. - Client addresses. nginx gives Krayin the address of the connection; forwarding headers sent by clients are replaced or removed. Behind a load balancer every request therefore comes from the load balancer's address.
- PHP. The PHP pool runs as
krayin, is confined to Krayin's code, data and configuration directories, and cannot start programs. - Uploads. Files under
/storage/are served as downloads with a sandboxing Content-Security-Policy and are never executed. - Outbound connections. Webhooks, IMAP, SMTP and any other address entered in Krayin are fetched by the
krayinuser, which the egress backstop keeps away from the metadata service and your VPC's private addresses (Step 12).
Support
SecureAnyCloud provides support for this image by email at sales@secureanycloud.com. Krayin documentation is at devdocs.krayincrm.com.
Deploy on AWS
Launch Krayin CRM 2.2.8 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com