Applications AWS

FreeScout 1.8.250 on AWS User Guide

| Product: FreeScout 1.8.250 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the FreeScout 1.8.250 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg help desk, IT service and project management listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

FreeScout is an open source help desk and shared inbox, a self hosted alternative to Help Scout and Zendesk. E-mail sent to your support addresses becomes conversations in shared mailboxes, where agents assign, reply to and close them, with saved replies, internal notes, tags, customer profiles, per mailbox permissions and notifications.

What the cloudimg image adds:

  • No web installer and no default login. Upstream FreeScout is set up in a browser installer that creates the first administrator. On this image the installer is not shipped and /install answers 404. First boot creates the database and one administrator, admin@cloudimg.local, with a password unique to your instance. There are no other users.
  • Nothing baked into the image. The image ships an empty MariaDB data directory and no FreeScout settings file, so every instance creates its own database and its own Laravel application key (APP_KEY, which signs cookies, attachment links and notification replies) on first boot.
  • No database password at all. FreeScout runs as its own system user, freescout, and MariaDB accepts that user only over the local socket from processes running as freescout (unix_socket authentication). MariaDB has no network listener.
  • HTTPS from the first boot. nginx serves FreeScout on port 443 with a certificate generated on your instance, port 80 redirects to HTTPS, requests for a host name the instance does not serve get no answer, and the session cookie is Secure, HttpOnly and SameSite=Lax.
  • Only FreeScout's front controller runs PHP. Every request goes to FreeScout's router; no other PHP file can be reached, and any path that starts with /index.php is refused, so no request can reach a page around the limits below. The settings file, logs, caches, sources and the web cron URL answer 404.
  • Read only code and safe uploads. The FreeScout code is owned by root and read only to PHP. Uploaded avatars and editor images are served by nginx as static files with a sandboxing content security policy, never run as PHP, and attachments are sent only by FreeScout through signed links.
  • Abuse limits and cross site protection. Sign in, password reset and invitation requests are rate limited per client address, the links that change data with a plain GET (undo a reply, clone a ticket, disconnect a mailbox) refuse requests opened from other sites, and FreeScout sees the real client address and the configured host name, never values a client sends in headers.
  • Metadata access blocked. nginx, PHP-FPM, MariaDB, first boot and the FreeScout scheduler, with the mail fetch and queue workers it starts, are blocked from the instance metadata service, so a mail server, webhook or image address pointed at it gets nothing back, and the AMI requires IMDSv2.
  • Background work from a service. FreeScout's scheduler, which fetches mail, sends replies and notifications and runs the queue, runs every minute from freescout-scheduler.service as the freescout user. No crontab and no web cron.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: FreeScout 1.8.250 (the official release from GitHub, checksum verified, with its bundled PHP libraries and no modules), nginx, PHP 8.3 FPM, MariaDB 10.11, 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 (FreeScout) and TCP 80 (redirects to HTTPS) from the networks your agents work from. Your customers' e-mail is in FreeScout, so keep 80 and 443 as narrow as you can.
  • A mailbox at your e-mail provider (IMAP or POP3 to fetch, SMTP to send) for each support address. FreeScout fetches mail; it does not receive it on port 25.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick FreeScout 1.8.250 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger for many agents or a busy mailbox), your key pair and a security group as above.

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=freescout}]'

First boot builds the database and takes about two minutes after the instance is running.

Step 2: Connect to your instance

Version SSH user
FreeScout 1.8.250 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot creates the database, the settings file and the administrator and mints the certificate; nginx and the scheduler start only after it has finished:

systemctl is-active nginx php8.3-fpm mariadb freescout-scheduler freescout-firstboot
sudo test -f /var/lib/cloudimg/freescout-firstboot.done && echo FIRSTBOOT_DONE

All five units are active and the first boot marker is present:

active
active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on network ports. MariaDB has no network listener at all and PHP-FPM uses a local socket:

sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {split($6, p, "\""); print $4, p[2]}' | sort
0.0.0.0:22 sshd
0.0.0.0:443 nginx
0.0.0.0:80 nginx
[::]:22 sshd
[::]:443 nginx
[::]:80 nginx

Step 4: Get your URL and password

sudo cat /root/freescout-credentials.txt

The file is readable by root only:

# FreeScout 1.8.250 - generated on first boot by freescout-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.

FREESCOUT_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
FREESCOUT_ADMIN_EMAIL=admin@cloudimg.local
FREESCOUT_ADMIN_PASSWORD=<FREESCOUT_ADMIN_PASSWORD>
#
# Open FREESCOUT_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to
# https) and sign in with the e-mail and password above. Then set your own e-mail in Your Profile and add a mailbox.
# MariaDB: unix socket only (no TCP listener, no database password: FreeScout runs as the OS user freescout);
#          sudo mysql freescout   (root signs in over the socket)
# Settings: /etc/freescout/freescout.env (owned by freescout; then: sudo freescout-artisan freescout:clear-cache)
# Artisan:  sudo freescout-artisan <command>          Scheduler: systemctl status freescout-scheduler
# Change the password:            sudo freescout-set-password
# Change the name it serves as:   sudo freescout-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/freescout-1-8-250-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from the EC2 instance metadata, and the certificate also names the public and private IP addresses. nginx answers only to the names in the certificate. The name is fixed at first boot, so if you stop and start the instance without an Elastic IP and its public address changes, run sudo freescout-set-hostname with the new name (Step 9), or better, give the instance an Elastic IP or your own DNS name.

To check the sign in end to end from the instance itself (HTTPS redirect, refused paths, the administrator password through the real sign in form, and a wrong password refused):

sudo freescout-cred-roundtrip
OK https 301 + no installer/tools/web cron/.env/sources + administrator sign-in + wrong password refused

Step 5: Sign in

Open the FREESCOUT_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own, Step 9). Sign in with the e-mail address admin@cloudimg.local and the password from Step 4:

The FreeScout sign in page with the Email Address and Password fields, the Remember Me box, the Login button and the Forgot Your Password link

A new FreeScout has no mailboxes, so the dashboard offers Manage Mailboxes. Once you add one (Step 6), the dashboard shows a card for each mailbox with its open conversations, or a reminder to configure its connection settings:

The FreeScout dashboard with the Support mailbox card, its address and the note that the mailbox connection settings are not configured yet

Make the account yours. Open the user menu (top right), Your Profile, and set your name and your own e-mail address; FreeScout signs in by e-mail address and sends password resets there. Then update FREESCOUT_ADMIN_EMAIL in /root/freescout-credentials.txt to match, because freescout-set-password looks the administrator up by that address. To choose your own password, use Change your password on the profile page, then delete FREESCOUT_ADMIN_PASSWORD from the credentials file so it does not hold an old password.

If you lose the administrator password, set a new one from the instance. Without an option it generates a random password and writes it to the credentials file; with --stdin it reads the password you type (24 to 72 letters and digits). Either way it clears the remember me token and signs every session out:

sudo freescout-set-password
sudo freescout-set-password --stdin

Step 6: Mailboxes and conversations

Create a mailbox. Choose Manage, Mailboxes, New Mailbox, enter the support address customers write to and a name, and choose which users work in it:

The FreeScout Create a mailbox form with the Email Address and Mailbox Name fields and the Create Mailbox button

Connect it to your mail provider. Open the mailbox's Connection Settings. Under Fetching Emails enter the IMAP (or POP3) server, port, user name and password of the support address, keep Validate Certificate on, select Save Settings and then Check Connection. Under Sending Emails choose SMTP and enter your SMTP server; Amazon SES works as the relay (port 587 with STARTTLS). The image has no local mail server, so PHP's mail() function and Sendmail cannot send:

The FreeScout mailbox Connection Settings, Fetching Emails tab with the IMAP protocol, server, port, username, password, encryption, IMAP folders and Validate Certificate fields

Work the queue. New mail arrives as conversations in Unassigned. Assign a conversation to an agent, reply, add a private note, tag it and close it; Mine, Assigned and Closed follow its progress, and the envelope button starts a new conversation yourself:

The FreeScout Support mailbox with its Unassigned, Mine, Starred, Assigned, Closed and Spam folders, empty on a new instance

Users. Add each agent under Manage, Users, New User, as a User (works in the mailboxes you grant) or an Administrator. FreeScout e-mails each new user an invitation link to set their password, so set up the system e-mail below first.

Step 7: Mail settings and the scheduler

System e-mail. Invitations and alerts to administrators use the system e-mail settings under Manage, Settings, Mail Settings. Set Mail From to an address at your domain and Send Method to SMTP with the same server as your mailboxes (the default, PHP's mail() function, has no mail server to use on this image). Fetching Interval sets how often every mailbox is checked:

The FreeScout Mail Settings page with the Mail From address, the Send Method, the Send Test field and the Fetching Interval

The scheduler and its workers. freescout-scheduler.service runs FreeScout's schedule:run once a minute as the freescout user. It fetches mail and starts the queue worker that sends replies and notifications, and every one of those processes stays inside the service, under the same metadata block:

systemctl show freescout-scheduler -p User -p IPAddressDeny
for p in $(pgrep -f '[a]rtisan queue:work'); do echo "$(ps -o user= -p "$p") $(cut -d: -f3 "/proc/$p/cgroup")"; done
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
User=freescout
freescout /system.slice/freescout-scheduler.service

The FreeScout console. FreeScout's artisan command line runs as the freescout user in a short lived systemd unit with the same metadata block as the services. Use it for maintenance such as clearing the cache after you edit the settings file:

sudo freescout-artisan freescout:clear-cache
grep -F "'version'" /var/www/freescout/config/app.php | head -1

The System Status page. Manage, System, Status shows the version, PHP and the background jobs. Two of its permission checks are expected to show a warning on this image: Modules/ is "Not writable" and public/storage (symlink) is "Not found". Both are deliberate: the code is read only to PHP, and nginx serves uploads itself. Do not run the chown command the page suggests; it would let PHP change its own code. For the same reason the Modules page is not available, and the web cron URL the page shows answers 404 (the scheduler service does that work).

What the server contacts. FreeScout connects only to the mail servers and other services an administrator configures. None of the services can reach the instance metadata service (Step 10). If you want to limit these connections further, restrict the instance's outbound traffic with its security group or a network ACL to your mail servers.

Step 8: Settings that stay off

FreeScout's settings file is /etc/freescout/freescout.env, owned by freescout with mode 0600. Debug output is off, sessions are files on the instance, the session cookie is Secure and SameSite=Lax, no proxy is trusted for client addresses and there is no database password:

sudo grep -E '^(APP_DEBUG|APP_TRUSTED_PROXIES|SESSION_DRIVER|SESSION_SECURE_COOKIE|SESSION_SAME_SITE|DB_PASSWORD)=' /etc/freescout/freescout.env
APP_DEBUG=false
APP_TRUSTED_PROXIES=
SESSION_DRIVER=file
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
DB_PASSWORD=

If you put a load balancer in front of the instance, set APP_TRUSTED_PROXIES to its addresses only, then run sudo freescout-artisan freescout:clear-cache.

Step 9: Your own DNS name and certificate

Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name FreeScout is served as. This mints a new self signed certificate for the name, points nginx and FreeScout's APP_URL at it, updates the URL in /root/freescout-credentials.txt, and keeps the name across reboots:

sudo freescout-set-hostname <your-domain>

To use a certificate from a public or internal certificate authority instead (for example one issued by Let's Encrypt for <your-domain>), copy it over the generated pair and reload nginx (running freescout-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/freescout.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/freescout.key
sudo nginx -t && sudo systemctl reload nginx

FreeScout sends its own Strict-Transport-Security header; browsers ignore it over the self signed certificate and honour it once your users' browsers trust the certificate.

Step 10: Security checks

nginx serves only the application. The installer, any path under /index.php, the settings file, logs, a PHP file under the uploads, the web cron, the Modules page and the vendored sources answer 404, plain HTTP redirects to HTTPS, and a request for a name the instance does not serve gets no answer at all:

H=$(sudo grep '^FREESCOUT_URL=' /root/freescout-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
for p in / /login /index.php /index.php/login /install /.env /storage/logs/laravel.log /storage/x.php /system/cron/x /modules/list /vendor/autoload.php; do
  curl -sk -o /dev/null -w "%{http_code}  $p\n" --resolve "$H:443:127.0.0.1" "https://$H$p"
done
curl -s -o /dev/null -w 'http:// -> %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'unknown host name -> %{http_code}\n' --resolve "other.example:443:127.0.0.1" "https://other.example/" || true
302  /
200  /login
404  /index.php
404  /index.php/login
404  /install
404  /.env
404  /storage/logs/laravel.log
404  /storage/x.php
404  /system/cron/x
404  /modules/list
404  /vendor/autoload.php
http:// -> 301
unknown host name -> 000

The links that change data with a plain GET are refused when the request comes from another site, so a link or image on another page cannot undo a reply, clone a ticket or disconnect a mailbox on your agents' behalf:

H=$(sudo grep '^FREESCOUT_URL=' /root/freescout-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
curl -sk -o /dev/null -w 'cross-site undo reply -> %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/conversation/undo-reply/1/1"
cross-site undo reply -> 403

FreeScout's database account has no password; MariaDB accepts it only over the local socket from processes running as freescout, and MariaDB does not listen on the network:

sudo mysql -N -B -e "SELECT CONCAT(User, '@', Host, ' authenticates by ', JSON_VALUE(Priv, '$.plugin'), ', password set: ', IF(IFNULL(JSON_VALUE(Priv, '$.authentication_string'), '') = '', 'no', 'yes')) FROM mysql.global_priv WHERE User = 'freescout'; SELECT CONCAT('skip_networking: ', @@skip_networking);"
freescout@localhost authenticates by unix_socket, password set: no
skip_networking: ON

The certificate was generated on this instance and names its own addresses:

H=$(sudo grep '^FREESCOUT_URL=' /root/freescout-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
    DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

None of the services can reach the instance metadata service, so a mail server, webhook or image address pointed at it gets nothing back:

systemctl show nginx php8.3-fpm mariadb freescout-scheduler freescout-firstboot -p Id -p IPAddressDeny
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=nginx.service

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=php8.3-fpm.service

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=mariadb.service

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=freescout-scheduler.service

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=freescout-firstboot.service

sudo freescout-artisan <command> runs FreeScout's console with the same block.

Step 11: Backup, storage and maintenance

Everything you enter is in the MariaDB database freescout (/var/lib/mysql); attachments, uploads, sessions, caches and FreeScout's logs are under /var/lib/freescout, and the settings file with the application key is /etc/freescout/freescout.env. All three are on the root volume, so the simplest backup is an EBS snapshot of it. For a consistent copy while FreeScout is running:

sudo mariadb-dump --single-transaction --routines freescout | gzip > <backup-dir>/freescout-$(date +%F).sql.gz
sudo tar -C / -czf <backup-dir>/freescout-files-$(date +%F).tar.gz var/lib/freescout/storage/app etc/freescout

Keep /etc/freescout/freescout.env with the dump: its APP_KEY encrypts the mailbox passwords FreeScout stores, and a restored database cannot use them without it. The dump and the settings file hold every password hash and secret, so store them as carefully as the instance.

To give attachments and the database more room, enlarge the root volume in the EC2 console (Volumes, Modify); Ubuntu grows the file system on the next boot. If you would rather keep FreeScout's data on its own EBS volume, attach a volume, stop FreeScout, copy /var/lib/mysql and /var/lib/freescout onto it, and mount it back over the same paths with bind mounts in /etc/fstab before you start the services again; contact support if you would like help with the move.

FreeScout's web updater and module installer cannot change the code on this image, because the code is read only to PHP. New FreeScout releases arrive as new versions of this listing, and support can help you move your data to one. Operating system security updates install automatically; run sudo apt-get update && sudo apt-get upgrade to apply everything else. nginx, PHP-FPM and MariaDB restart cleanly and FreeScout keeps all of its data.

Component Path
FreeScout /var/www/freescout (owned by root, read only to PHP; nginx serves public/)
Settings and application key /etc/freescout/freescout.env (owned by freescout, mode 0600)
Database MariaDB database freescout (/var/lib/mysql)
Attachments, uploads, sessions, caches and logs /var/lib/freescout
nginx site /etc/nginx/sites-available/cloudimg-freescout
Certificate and key /etc/nginx/ssl/freescout.crt, /etc/nginx/ssl/freescout.key
Scheduler freescout-scheduler.service
Helpers /usr/local/sbin/freescout-set-password, freescout-set-hostname, freescout-artisan, freescout-cred-roundtrip
Credentials /root/freescout-credentials.txt (mode 0600)
Source and changes /usr/share/doc/freescout/SOURCE.txt

Step 12: Security recommendations

  • Allow port 22 from your admin network only, and ports 80 and 443 only from the networks your agents work from.
  • Use your own DNS name and a certificate your users' browsers trust (Step 9).
  • Set your own e-mail address on the administrator and change its password (Step 5); give each agent their own user with access only to the mailboxes they work in.
  • Use dedicated support mailboxes with their own passwords (or app passwords) for fetching and sending, and keep Validate Certificate on.
  • Keep the operating system patched, and snapshot the root volume regularly.

Support and licensing

FreeScout is open source under the GNU Affero General Public License version 3, by the FreeScout project and its contributors; there is no per agent or per mailbox licence fee. The image ships FreeScout exactly as released, with its bundled PHP libraries and no modules: /usr/share/doc/freescout/SOURCE.txt names the release it came from and lists every file the image leaves out or adds, and no source file is modified. 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

All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.