One of the first things you run into when moving a Laravel project to shared hosting is this: a command that appears in every tutorial fails silently on your server or throws a strange error. The cause is almost always one line in the PHP configuration that nobody showed you when you bought the hosting: disable_functions.
This post explains exactly what that line breaks, how to see it, and what replacement actually works for each case.
What disable_functions is and why it exists#
disable_functions is a list of PHP function names that the interpreter refuses to call. Any call to them produces a fatal-level error, even if the related extension is installed.
On a shared server, dozens or hundreds of accounts live on one machine. If one site gets hacked and the attacker can run system commands, the whole server is at risk. Blocking command-execution functions is the cheapest and most effective defense. So this restriction makes sense and will probably not be lifted — instead of fighting it, fit your architecture to it.
See it first#
There are three ways to see the list, and you should try all three, because the command-line value can differ from the web value.
From the terminal:
php84 -r 'echo ini_get("disable_functions"), PHP_EOL;'From inside the application itself (for example a temporary route or an artisan command):
dd(ini_get('disable_functions'));And in cPanel, the "Server Information" section or phpinfo shows the same value.
On the server this site runs on, the list includes: exec, shell_exec, system, passthru, popen, proc_open, symlink and highlight_file. Below we look at exactly what each of them takes away.
1. Command-execution functions: exec and its family#
exec, shell_exec, system, passthru, popen and proc_open all do one thing: they run a system command from inside PHP. When they are blocked, there is no way left to call an external binary from PHP.
Take this sentence seriously, because many common solutions depend on exactly that:
| What you wanted to do | Common tool | Status on shared hosting |
|---|---|---|
| Resize and optimize images | ImageMagick via command | Impossible |
| Convert and compress video | ffmpeg | Impossible |
| Build a PDF from HTML | wkhtmltopdf or chromium | Impossible |
| Optimize images | jpegoptim and optipng | Impossible |
| Back up the database from inside the app | mysqldump | Impossible |
Real alternatives#
For images: the GD extension. GD is part of PHP itself, needs no system command, and covers resizing, rotation, transparency and WebP output. Its quality is perfectly adequate for common web work. We built this site's whole media pipeline on plain GD; how, and with which safeguards.
Check that it is installed at all:
var_dump(extension_loaded('gd'));
var_dump(function_exists('imagewebp'));For PDF: pure-PHP libraries such as the dompdf and mpdf families. They are slower and less precise, but need no external binary.
For database backups: instead of calling mysqldump from PHP, put it in a cron job. Cron runs from the shell and disable_functions has no effect on it. This is an important difference: the restriction is on the PHP interpreter, not on your user account in the shell.
For video: do not take it seriously on shared hosting. Use an external service or a separate server.
2. The symlink function and the storage:link story#
If symlink is blocked, the well-known command below cannot work by design:
php84 artisan storage:linkTo link public/storage to storage/app/public, this command uses the symlink function. If it is blocked, it cannot be done.
Good news: it can still be done from the shell. The ln command is a system binary and has nothing to do with PHP settings:
ln -s ~/app/storage/app/public ~/public_html/storageOn cPanel there is also a second trap that can make the result wrong even if symlink is open — we wrote the whole story in a separate storage:link post.
3. The highlight_file function: the worst one, because it is silent#
This one looks less important than the others, but in practice it cost us the most time.
highlight_file prints the code of a PHP file with syntax colouring. Nobody calls it directly — but Laravel's pretty error page uses something from the same family to show the code snippet at the error location.
The result is a vicious loop:
- A real error occurs in your code.
- Laravel tries to render the error page with the code snippet.
- The renderer hits the blocked function and throws its own error.
- What you see in the browser is the step-three error, not the step-one error.
That means on a server like this, turning on debug mode hides the real cause. If you have ever struggled with a vague, unrelated error on shared hosting, this was probably it. We wrote about it in full here.
4. Functions that are not blocked but are limited#
There are a few more cases you will not see in the disable_functions list but that behave similarly:
set_time_limitmay have no effect because the web server sets the real limit.ini_set('memory_limit', -1)usually cannot go above the account's cap.putenvis sometimes blocked, and some libraries silently fall back to default behaviour.opcache_resetis usually disabled on shared hosting; to clear the code cache you have to rely on other methods.
5. Packages that fail silently#
The worst case is not you calling a blocked function — you notice that quickly. It is worse when a package in the vendor folder does it and the failure looks like something else.
A few common examples:
- Image libraries that choose the ImageMagick driver by default and only use GD when you configure it explicitly.
- Backup packages that run a command to dump the database and stop with a vague "process failed" error.
- PDF generation tools that run a headless browser behind the scenes.
- Image optimization libraries that are only a wrapper around a few external binaries and silently do nothing without them.
Practical rule: before adding any package that deals with files or media, look for the word "binary" or "requirements" in its documentation. If it needs an external binary, it is not an option on shared hosting.
The decision we made was more radical: no new package without review, and we write the small tools ourselves. The result is an app/Support folder with small, specific classes — a multilingual text normalizer, an image uploader, a slug generator — each only a few dozen lines, and we know exactly what they do. For a medium-sized project, this costs less than debugging an incompatible package.
6. Quick diagnosis: an environment check script#
Instead of guessing every time, print everything once. Run this in the terminal:
php84 -r '
$check = ["exec","shell_exec","system","passthru","popen","proc_open","symlink","highlight_file","putenv","set_time_limit"];
foreach ($check as $f) {
printf("%-16s %s\n", $f, function_exists($f) ? "available" : "blocked");
}
printf("%-16s %s\n", "gd", extension_loaded("gd") ? "available" : "missing");
printf("%-16s %s\n", "imagewebp", function_exists("imagewebp") ? "available" : "missing");
printf("%-16s %s\n", "memory_limit", ini_get("memory_limit"));
'Keep the output somewhere. Every time you make an architecture decision, a glance at it takes ten seconds and saves hours.
Run the same thing over the web too, because the values can differ. Use a temporary Laravel route that only an admin can reach, or an artisan command — and delete it afterwards.
7. When you really need a capability#
If a capability is critical and the host does not provide it, three options remain, and their order matters:
1. A pure-PHP alternative. Always the first choice. It is slower but has no external dependency.
2. An external service. For heavy work such as video processing, a separate service called over an API. It costs money, but on shared hosting it is sometimes the only real way.
3. Upgrade to a VPS. If the project has reached a point where several critical capabilities are impossible on shared hosting, fighting the limits costs more than migrating.
What you should not do: look for a way around disable_functions. Any method that claims to bypass this restriction either does not work or is a vulnerability, and using it puts you in conflict with the terms of service and can lead to account suspension.
How to make your code resilient to this situation#
Three practical rules from our own experience:
1. Never assume a function exists. Check before use and have a fallback path:
if (! function_exists('imagewebp')) {
// fallback path or a clear message to the user
}2. Move shell-dependent work to cron. Cron has a full shell. Backups, cleanup, batch processing — they all belong there.
3. Give a clear error message, not a silent failure. The worst case is when a user uploads a file, sees no error, and the file was not saved. If a function is missing, say clearly which capability is unavailable.
Summary#
You cannot bypass disable_functions, and you should not try; it is part of the reason shared hosting is safer than it could have been. What you can do is know what is unavailable before you write code and not build your architecture on something that does not exist.
If you are just choosing a host, check this together with the rest in the shared hosting checklist for Laravel; it covers six more things to ask before you buy.