How Zammad Actually Stores Media

Before touching any setting, it helps to understand that Zammad does not behave like WordPress, Joomla, or a classic gallery CMS. There is no media library that generates a pyramid of resized thumbnails, no on-the-fly WebP/AVIF conversion pipeline, and no built-in CDN offloader module. Zammad is a Ruby on Rails helpdesk application, and nearly everything it handles as "media" is an attachment: files that arrive by email, files agents drag into a ticket reply, inline images pasted into the rich-text editor, and avatar images uploaded through user profiles. Understanding this distinction is what keeps you from chasing optimizations that simply do not exist in the product.

Zammad supports two storage backends for attachments, controlled from the admin area under Admin → System → Storage (the Storage Provider setting). The first is Database, which writes every attachment as a binary blob into the store_file / store_object tables inside your MySQL/MariaDB database. The second is Filesystem, which writes files to disk under the application's storage/fs/ directory while keeping only metadata in the database. Zammad defaults to database storage because it is self-contained and survives easily across backups, but on a shared hosting account that default is frequently the root of your storage pain.

The reason is quota accounting. Shared hosting plans meter disk usage, and many also impose per-database or total MySQL size ceilings. When attachments live in the database, every PDF, screenshot, and email-embedded logo inflates the database. Large InnoDB tables are slower to back up, slower to repair, and they consume the same disk quota you need for the application itself. A 2 GB mailbox worth of attachments inside store_file is far more operationally awkward than the same 2 GB sitting as flat files you can see and manage in cPanel File Manager or DirectAdmin's file browser. For this reason, the single most effective "optimization" on shared hosting is usually switching the storage provider to filesystem, which Zammad can do while migrating existing blobs out of the database automatically.

Switching to Filesystem Storage and Reclaiming Database Space

Changing the backend is an admin-dashboard operation, so it stays well within an unprivileged hosting account's capabilities. Log into Zammad as an admin, open Admin → System → Storage, and change the provider from Database to Filesystem. When you save, Zammad begins moving existing attachments from the database to the filesystem as a background task, so the switch is non-destructive and reversible. Files land under the Zammad installation path, typically something like ~/zammad/storage/fs/ relative to your account home, organized into sharded subdirectories by a content hash.

After the migration completes, the database blobs are purged, but the InnoDB tablespace does not shrink on its own. The physical files on disk (the ibdata/table files) stay allocated even though the rows are gone. On shared hosting you cannot run server-level OPTIMIZE maintenance or touch my.cnf, but you can reclaim space per table through phpMyAdmin. Open phpMyAdmin from cPanel or DirectAdmin, select your Zammad database, locate the store_file table, and use the Operations tab's Optimize table action. This issues a standard OPTIMIZE TABLE against that single table, which is permitted for a normal database user and rebuilds it to release freed space back to the filesystem. Do this during a quiet period, since the operation briefly locks the table.

Two caveats matter here. First, confirm your hosting plan actually counts filesystem storage and database storage against the same quota or separate ones; if they are separate, moving blobs from DB to disk simply shifts where the pressure appears. Check the Disk Usage and MySQL Databases sections in cPanel (or the equivalent panels in DirectAdmin) to see both numbers. Second, make sure the storage/ directory is included in your backup routine once attachments live there, because a database-only backup will no longer capture file content.

Controlling Upload Size, Image Dimensions, and PHP Limits

Zammad serves its web traffic through a bundled application server, and the PHP Selector or MultiPHP controls in cPanel/DirectAdmin do not govern the Rails process the way they would for a PHP application. This is an important expectation to set: the usual .user.ini trick of raising upload_max_filesize and post_max_size affects PHP scripts, not Zammad's Ruby upload handling. Where those PHP tunables still matter is any companion PHP tooling you run in the same account, such as phpMyAdmin imports or a separate status page. If you maintain such a script, you can still place a .user.ini beside it with entries like the following, and it will apply only to that PHP context:

; .user.ini for an auxiliary PHP tool in the same account
upload_max_filesize = 32M
post_max_size = 36M
memory_limit = 256M

For Zammad itself, attachment size is governed inside the application and by whatever reverse proxy sits in front of it. Zammad enforces a maximum attachment size through its own configuration (the maximum_attachment_size setting, expressed in megabytes), adjustable by an administrator. Keeping this value conservative — many helpdesks are comfortable in the 10 MB to 20 MB range — directly limits how fast storage grows and prevents a single oversized email attachment from bloating the database or disk. Lowering the ceiling is a legitimate media optimization on constrained hosting because it caps worst-case growth at the source.

If your setup routes Zammad through LiteSpeed via a proxy or reverse-proxy rule that your host configured, request body size limits still apply at that layer. You generally cannot edit server-level LiteSpeed limits as an unprivileged user, but you can keep the Zammad-side maximum below any host-imposed proxy limit so uploads fail gracefully with a Zammad error rather than a raw 413 from the web server. On the image side, Zammad does not resample or re-encode attachments into WebP or AVIF; an image that arrives as a 6 MB PNG stays a 6 MB PNG. The practical levers are therefore educating agents to paste compressed screenshots, and using the attachment-size cap to reject the largest files before they ever persist.

Delivery, Caching, and the CDN Reality

Attachment delivery in Zammad is authenticated and ticket-scoped: a file is served through an application route that checks the requesting user's permissions before streaming the bytes. That access-control requirement is why you cannot simply park attachments on a public CDN or object-storage bucket and point Zammad at a URL — the current product does not ship an S3-compatible object-storage driver or a CDN offload module for attachments, so any guide promising that integration is describing software Zammad does not have. What you can meaningfully accelerate on shared hosting are the static assets: the compiled JavaScript, CSS, fonts, and icon sprites that make up the agent and customer interface.

Those static files are fingerprinted (they carry a content hash in the filename), which makes them safe to cache aggressively. If your host lets you place rules that reach the static path, a long-lived cache policy pays off immediately. Where a .htaccess context applies to assets served from your account, rules like the following set far-future expiry for immutable, fingerprinted files:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|woff2)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

Keep those cache directives away from the attachment-download route, since caching permission-checked content at an intermediary could expose a file to the wrong user. A genuine CDN can still front your Zammad hostname for the static layer if your host supports it, but treat attachments as always-dynamic. For the deeper mechanics of asset fingerprinting and browser cache headers on this platform, the companion article on Zammad performance and caching covers the asset-delivery side in detail, and this storage-focused guide is meant to sit alongside it rather than repeat it.

The honest summary for a shared account is a short list: migrate attachments to filesystem storage, reclaim the vacated space with a per-table optimize in phpMyAdmin, cap the maximum attachment size in Zammad's admin area to control growth, and reserve caching and any CDN layer for fingerprinted static assets only. Those four moves are the real, supported optimizations — everything beyond them waits on features Zammad has not yet added.