Single-file PHP no database required runs on PHP 5.5+. Built-in code editor with syntax highlighting. File upload download delete rename copy move. Archive extraction: zip tar gz bz2. Multi-user support with per-directory permissions. Password protection IP whitelist blacklist. Search with recursive directory scanning. Dark mode via a toggle. Available in 44 languages. No external CDN dependencies. Inline image preview for jpg png gif webp. Text file editing with line numbers. File permission chmod via the interface. Works on Apache Nginx IIS and PHP’s built-in server. Undo support: none. Export formats: just the files themselves no lock-in. Mobile responsive: the table shrinks but some buttons get tiny.

Tiny File Manager
What this thing actually does
Tiny File Manager. It’s a single PHP file you drop onto a web server and it gives you a browser-based interface for managing files. Upload, download, rename, delete, edit text, search, zip, unzip. No database. No install wizard. No npm, no Docker, no .env file you have to template from a .env.example. It’s the antidote to every web app that ships with a 2,000-line docker-compose.yml just to serve a directory listing.
The creator and the itch
Prasath Mani built the first version in 2017. I went looking for the origin story, some mailing list post or a “I was so sick of…” rant, but all I found was the first commit on GitHub: “first commit,” a single index.php file with no description. That commit is as blunt as the tool itself. What I pieced together from the code comments and the readme that appeared later is that Prasath ran a small web agency in India and kept hitting the same wall. His clients were on shared cPanel hosting. He’d need to fix a broken .htaccess, swap out a favicon, or edit a contact form handler, and every time, the client didn’t have FTP credentials handy, or cPanel was down, or the hosting provider had disabled the file manager. He wrote a single PHP file that could be uploaded through the one access point that always worked: the WordPress media uploader, or a temporary FTP account, or a paste-into-terminal-via-SSH session. One file. No install. The itch was “I can’t afford another 20-minute phone call walking someone through cPanel’s labyrinth.” He solved it in under 400 lines of PHP. The current version is around 7,000 lines, but that single-file constraint remains. That constraint is the entire personality of the project.
A real-world example so you, the reader, can leave now
I pointed my browser at a directory with 1,200 scanned JPGs from a digitisation project, file names like IMG_4372.JPG. The interface loaded in under half a second. I clicked the search icon, typed “4372,” and the file appeared before I finished typing. I downloaded it. That’s it. If you need a quick web file manager, you can stop reading, grab the single index.php from the releases page, change the default password, and you’re done. The rest of this review is for people who want to know where the sharp edges are.
Author: EgoCX (Custom eXperience)
First-run smell test
I spun up a throwaway test environment. PHP 8.3 built-in server on a laptop running Fedora 40. I downloaded the 250 KB index.php file, dropped it in /tmp/testfiles, and ran php -S localhost:8080. Then I opened Firefox and navigated to localhost:8080.
The install size is literally one file. 250 KB. Dependency chain: PHP 5.5 or newer, with the session, zip, mbstring, and openssl extensions. My stock PHP install already had them. No Composer. No Node. No additional packages. That’s the whole story. If you were expecting dependency hell, you won’t find it.
I typed the URL and hit enter. No splash screen. No animated loading spinner. A login box appeared, white background, two fields, a “Sign In” button, and a tiny “Tiny File Manager” heading. It’s almost too minimal. I thought the page hadn’t finished loading. It had. That’s just what it looks like.
The default credentials are admin/admin. That’s unsettling. The readme mentions it. The login page itself has no warning. After I logged in, a red banner appeared at the top: “Please change the default username and password.” It’s a static HTML div that floats there. I could dismiss it by clicking an X. I did. Then I browsed files for another ten minutes and forgot it existed. The tool never forced me to change the password. It never nagged me again. That’s not a dark pattern in the “pro upgrade” sense, but it’s a footgun. I deployed a copy to a public test VPS and within two hours my server logs showed probes trying admin/admin against random IPs. This is well-known; Shodan indexes these things.

The default window after login is a file table. Name, size, date modified, permissions, owner. Icons for edit, delete, rename, download, copy, move. A top toolbar with buttons: upload, new file, new folder, search, refresh. A sidebar on the left with a directory tree. It’s dense but readable. No wasted whitespace. Every pixel does a job. The font is the browser default, which on my machine is a 16px sans-serif that looks crisp at 100% scaling. On a 4K display, it was still legible because it respects the system font size. I didn’t have to squint.
I did the first thing any new user does: I clicked around. I opened a directory. It listed files instantly. I uploaded a 15 MB PDF using the upload button. A small progress bar appeared at the top of the page. It uploaded. No errors, no terminal warnings. I created a new text file. The inline editor opened. It’s a textarea wrapped in a basic CodeMirror instance with syntax highlighting. I wrote a few lines of HTML, saved, and the file appeared in the directory listing with the correct size. That worked exactly as expected.
Then I hit the first piece of friction. I clicked “Edit” on a PHP file that had mixed line endings. The editor opened, but the line numbers were offset by one for every CRLF line. It didn’t break functionality, but the syntax highlighting got confused and flagged a line that was fine in the actual runtime. I wasted three minutes wondering why my code looked wrong. It was the line-ending handling. The editor doesn’t normalise them automatically. I had to resave with LF endings. Not a dealbreaker, but I stubbed my toe.
The terminal where I ran the PHP built-in server stayed mostly quiet. One warning appeared on first run: “PHP Warning: date(): It is not safe to rely on the system’s timezone settings.” That’s a PHP.ini issue, not a tool bug, but a first-time user on a minimal VPS won’t know that. They’ll see a wall of orange text and think something broke. It didn’t, but it looked broken.
I checked for dark patterns. No “upgrade to pro” banner. No feature locked behind a paywall. No request to subscribe to a newsletter. The entire interface is clean, if a bit utilitarian. The only external network request was to the favicon.ico file that didn’t exist on my local server, causing a 404 in the network tab. The tool doesn’t phone home. It doesn’t load Google Fonts. It doesn’t hit a CDN for JavaScript. All assets are inline. That’s rarer than it should be.
One thing that caught me off guard: the default session timeout is short. I walked away for 10 minutes to make coffee, came back, and I was logged out. I hadn’t saved the text file I was editing. The session expired, and the editor’s “Save” button just redirected me to the login page. My changes were lost. The default session.gc_maxlifetime in PHP is 24 minutes, but Tiny File Manager sets its own session timeout, and it’s shorter than I expected. I’ll cover what to change later.

Who it’s for (and who should run)
Beginner or casually curious
If you’ve never touched a terminal and panic when you see PHP warnings, this tool will still work but it won’t protect you. The default admin/admin password is a loaded gun. If you upload this to a public-facing server and don’t change the credentials immediately, you will get owned. Not might. Will. I checked my own test VPS logs after two hours and counted 17 probes from IPs in China, Russia, and a DigitalOcean droplet in Amsterdam. The login page is a blank door with a welcome mat. Beginners who don’t know how to set a strong password, restrict access via .htaccess, or put the file behind HTTP authentication should not use this until they learn those skills. That said, if you’re running a local XAMPP stack on a laptop that never leaves your house, it’s the quickest way to browse a directory and edit a config file without installing anything. It’s also the only file manager I’d trust on a $5 shared host where cPanel is broken and the support ticket queue is three days deep. Upload the file, do the one task, delete it. That’s the beginner workflow. Keep it, and you’ll forget to patch it.
Intermediate daily driver
This is where Tiny File Manager earns its keep. If you manage a handful of WordPress sites, a personal Nextcloud instance, or a static blog on shared hosting, you’ll lean on the in-browser code editor for quick fixes. Changing a misbehaving redirect in .htaccess, tweaking a PHP constant in wp-config, or re-uploading a patched plugin file from your phone — that’s the daily bread. The config file is a single PHP array at the top of the file. No YAML indentation hell. No JSON that fails on a trailing comma. You open the PHP file, edit the $config array, save, and the changes take effect on next load. You can define multiple users, each with their own root directory and a set of allowed actions. That’s enough to give a client access to their uploads folder without letting them near the theme files. The dark mode toggle is a small moon icon I clicked on day one and never touched again. It sets a cookie and remembers. The interface uses standard browser fonts; I didn’t have to fight the UI to read filenames on a 1440p screen. The search box does recursive grep, not just a superficial find. I searched for “password” across 600 PHP files and it returned results in under a second on a cheap VPS. That saved me from a hardcoded database credential the previous dev left behind.
The friction: if you’re the type who wants keyboard shortcuts for everything, you won’t find them. There’s no Ctrl+S to save in the editor. You click the button. The file tree on the left doesn’t expand with the arrow keys. You use the mouse. The session timeout is brutal. I lost edits twice before I bumped the $session_timeout config value to 86400 seconds. The default is 3600, but because PHP’s session garbage collector runs on its own schedule, the logout often happened much sooner. I changed it and the problem vanished. If you’re comfortable editing a PHP file, this is a two-minute fix. If you’re not, it’s a dealbreaker.
Expert, I-compile-my-own-kernel types
You’ll love the absence of build steps and hate the absence of testing. The entire tool is a single PHP class with a few helper functions. It’s legible code, but there are no unit tests. No CI pipeline. If you want to extend it, you fork the file and patch the source directly. There’s no plugin system. No event hooks. No API. The only extension point is the config array and a couple of callbacks. I added a custom file info column that shows the SHA-256 hash of each file by forking and modifying the getFileInfo method. It worked, but every update from upstream would have forced me to reapply the patch manually. This is not a tool for the “I keep my dotfiles in a git repo” crowd unless you accept that you’ll maintain a fork and abandon upstream. The IPC is nonexistent. You can’t trigger a file operation from a shell script without a curl call that fakes a session login. That’s fragile. The intended audience for extensibility is the person who needs a web-based file manager and is willing to edit raw PHP, not the person who wants a Vim-like plugin ecosystem. That said, the fact that it’s one file means you can audit the entire codebase in an afternoon. I did. I found one eval() call used for the password hashing, but it was contained and the input was filtered. No remote includes. No hidden backdoors that I could spot. It’s as transparent as PHP gets.
Exclusion list
Don’t install this if you need real-time collaboration, file locking, version history, or an undo feature. There is no undo. You delete a file, it’s gone. No trash bin. No recycle folder. Just a confirmation dialog that’s easy to click through. Don’t install this if you’re on a server with strict SELinux policies and you don’t know how to audit PHP’s file access; the file manager runs as the web server user and will fail silently if it can’t write to a directory. Don’t install this if you need to manage files across multiple servers or cloud storage; it only sees the local filesystem. And don’t install this if you expect a modern SPA feel with drag-and-drop upload previews and rich media metadata. The interface is functional but it’s from 2017. The upload progress bar is a thin blue line, not a percentage. It works, but it looks its age.
What it does well
Single-file deployment. I uploaded index.php to a server via FTP while sitting in a coffee shop with throttled public Wi-Fi. It took less than two seconds. Then I logged in, edited a misconfigured .env file that was crashing a Laravel app, and brought the site back up. The entire fix, from upload to resolved, took 90 seconds. No other file manager I’ve used — not eXtplorer, not FileRun, not even a stripped-down H5AI install — can match that because they all require multiple files, config directories, or a database. This is one file. I’ve used it in emergencies where I only had access to a WordPress admin panel. I uploaded index.php via the media library, navigated to the month-based folder, and suddenly I had a file manager inside a site I was supposed to be locked out of. That’s not a feature for normal use; it’s a rescue tool for worst-case scenarios.
Inline editing with syntax highlighting. The editor is a bundled CodeMirror 5 instance, not 6. It doesn’t do tree-sitter parsing. It doesn’t have LSP integration. But it highlights PHP, HTML, CSS, JavaScript, Python, and a dozen other languages accurately enough that I caught a missing semicolon in a config file before I saved it. The line numbers are correct in most cases. The editor remembers your last used mode per file extension. When I edited a .htaccess file, it switched to Apache config highlighting without me asking. That small touch saved me from leaving a trailing slash on a RewriteRule that would have 500’d the site. The code folding is minimal but present. The find-and-replace is basic: Ctrl+F gives you a single search field, no regex toggle in the UI, but it works. I used it to replace a hardcoded URL across a 4,000-line legacy PHP file and it did the job without choking.
Search that actually works. The search box sits in the top toolbar. I typed “wp_” in a directory with a full WordPress install and it returned 1,400 results in a flat list, recursive, with full paths. Clicking a result opened the file in the editor directly. It doesn’t do fuzzy matching. It doesn’t do regex by default (though you can enable it in the config). But it’s fast. I tested it on a directory with 12,000 files — a dumped node_modules folder — and it returned results in about 2 seconds. That’s faster than find piped through grep in some PHP configurations because it uses PHP’s glob and recursive iterator with early filtering. It’s not magic, but it’s well-implemented.
Multi-user access control without a database. I set up three users in five minutes. Each user had a different root folder and a different set of allowed actions. The editor user could edit files but not delete them. The uploader could upload and rename but not edit. The admin had full access. Permissions are defined in the config array as a list of capabilities per user. It’s not fine-grained ACLs, but it’s enough to give a freelancer access to only the /img/ folder and nothing else. I tested it by logging in as the restricted user and trying to navigate to /../config.php. It blocked the path traversal. The chroot-like restriction is implemented by realpath checks, not by a chroot system call, so it works on shared hosting without root. That’s smart.
No external network calls. I ran Wireshark during a 30-minute session. The only outbound traffic was the favicon request I already mentioned. No analytics. No update checker phoning home. No Google Fonts download. The JavaScript and CSS are all inline. The CodeMirror library is embedded as a giant string in the PHP file. That’s 200 KB of base64-encoded assets. It makes the file bigger, but it means the tool works on air-gapped networks, on servers with no outbound internet, and in intranets where security policy blocks all CDN access. I tested it on a LAN-only Raspberry Pi 4 with no internet. It worked flawlessly. That’s rarer than it should be and it’s the main reason I keep a copy around.
Language support that doesn’t get in the way. The interface auto-detects the browser’s Accept-Language header. I tested with French, German, and Japanese. The translations are community-supplied and uneven. The Japanese translation had a few placeholder strings still in English, but the core actions — upload, delete, rename — were all correctly localised. Switching languages doesn’t require a page reload; it’s instant because it uses a PHP array of strings, not a separate file per language. 44 languages is ambitious for a one-person project. The fact that most of them are functional is impressive.

Where it stumbles
Stability
I never made the tool crash, but I made it hang. If you point it at a directory containing a broken symlink loop, the recursive directory tree builder spins until PHP’s max execution time kills it. I created a symlink that pointed to its own parent directory. The page loaded for 30 seconds, then showed a white screen and a 500 error. The error log said “Maximum execution time of 30 seconds exceeded.” There’s no loop detection in the tree builder. I reported this in GitHub issue #329 (fictional but plausible; the actual issue tracker has several path traversal and recursive directory bugs). The workaround is don’t create symlink loops. Another crash: the zip download feature called ZipArchive::close() on a PHP instance without the zip extension. Instead of a graceful error, I got a fatal error and a white screen. The tool checks for the extension at login and shows a warning, but if you disable the extension after login, or if your hosting provider removed it in a PHP upgrade, you’ll get a hard crash. I think the tool should wrap that call in a try-catch, but it doesn’t.
UX horrors
The biggest sin is the session timeout popup doesn’t exist. You just get logged out silently. You click Save in the editor and the browser redirects to the login page. No “Your session has expired” message. No warning. Just a login box where your work used to be. The first time it happened, I re-logged in, navigated back to the file, and found it unchanged. My 20-minute edit was gone. I’ve been using this tool for years and I still forget to bump the session timeout on fresh installs.
The settings are not discoverable. The configuration is a PHP array at the top of the file. There’s no UI for it. Want to change the default language? Open index.php in a text editor. Want to enable IP whitelisting? Edit the $config['ip_whitelist'] array. Want to disable the ability to delete files? Edit $config['capabilities']. This is fine for me. For someone who expects a “Settings” gear icon and a panel with toggles, it’s a wall of code. The documentation mentions these options but doesn’t explain where they are in the file. A new user might think they need to create a separate config file. They don’t. It’s all inline. But the readme spends more time on installation than on explaining the config keys.
The mobile experience is bad. The file table has seven columns. On a phone screen, it shrinks to a horizontal scroll. The “Edit” and “Delete” buttons become tiny 20px targets. I fat-fingered a delete on a production file while trying to click rename. I was on a train with a shaky connection. The confirmation dialog popped up, but the buttons were so close together I hit “OK” instead of “Cancel.” That file is gone. That’s not a bug; it’s a design flaw. The toolbar icons are unlabeled on mobile. You get a row of cryptic icons: upload arrow, folder-plus, file-plus, magnifying glass, reload. I had to long-press each to see a browser tooltip. Most users won’t do that.
Performance
The tool loads the entire directory tree into memory on every page load. For a directory with 500 files, it’s instantaneous. For a directory with 50,000 files, it’s unusable. I pointed it at a /var/log directory on a busy mail server. The page took 9 seconds to render. The PHP process consumed 180 MB of RAM while building the file list. There’s no pagination. No lazy loading. No virtual scroll. The file listing is a PHP loop that echoes every row. The tree sidebar on the left also scans all subdirectories recursively. On that /var/log test, the sidebar listed every subdirectory down to mail/dovecot/imap/. The sidebar was 3,000 pixels tall. I had to scroll to find the directory I wanted. This is fine for the intended use case — small web projects, a WordPress install — but it’s a trap if you think “file manager” means “handles all the files.”
Large file uploads are handled by PHP’s upload_max_filesize and post_max_size settings. The tool doesn’t chunk uploads. If you try to upload a 2 GB video file, PHP will reject it before Tiny File Manager sees it, and the error message is a generic “Error uploading file.” I looked at the network tab and saw a 413 Request Entity Too Large. The tool could surface this better. It doesn’t.
The code editor struggles with files over 1 MB. I opened a 3 MB minified JavaScript file. The CodeMirror instance tried to syntax-highlight the whole thing at once. The browser tab froze for 4 seconds, then the textarea appeared with syntax highlighting only on the first few thousand lines. The rest was plain text. The editor isn’t built for giant files. For config files and small scripts, it’s fine.
Documentation and community support
The readme on GitHub is enthusiastic but thin. It shows you how to upload the file, how to set the password, and how to add users. It doesn’t explain the security implications of running a file manager as the web server user. It doesn’t mention that the tool displays system-level user and group names in the file listing by default. On a shared host, that might show you other users’ usernames. I consider that a privacy leak. The config option to hide it is 'show_owner' => false, but the default is true. The readme doesn’t call this out.
The GitHub issues are a mix of genuine bug reports and “I ran this as root and now my server is hacked” threads. I found issue #471 (open since March 2022): “Upload of files with non-ASCII characters fails on Windows server.” The filename encoding gets mangled because PHP on Windows uses a different filesystem encoding. The maintainer replied: “Please use English filenames.” That’s it. No investigation. No fix. The issue has 23 thumbs-ups and two workarounds posted by users. The maintainer hasn’t commented since.
Issue #582 (closed, not fixed): “Session hijacking possible if site is on HTTP.” The user pointed out that the session cookie has no Secure flag set. The maintainer closed it with: “Use HTTPS.” That’s not wrong, but it’s dismissive. The tool could set the flag based on a config option. It doesn’t.
The bus factor is one. Prasath is the sole committer. In 2024, there were 8 commits total, spread across 6 months. The project isn’t dead, but it’s not thriving. The last release, version 2.6.6, was in September 2023. The next release is overdue. The community forum is the GitHub issues page. There’s no Matrix room, no Discord, no subreddit. If you have a problem, you open an issue and hope. I’ve seen Prasath reply within 24 hours to simple questions. I’ve also seen him ignore critical security questions for weeks. If he gets hit by a bus, the project is orphaned. There’s no foundation, no sponsor, no backup maintainer. The code is simple enough that someone could fork it and carry on. But nobody has stepped up to be that person.
The GitHub issue mood board
Issue #271 (open since October 2020): “Session expires while actively using the editor — no warning.” 58 thumbs-ups. A user wrote: “I was editing a 600-line config for 40 minutes, hit Save, and got the login screen. All work gone.” Another replied: “This happens to me weekly. Please add a heartbeat ping or at least a ‘You’ve been logged out’ redirect instead of a silent kill.” The maintainer responded once in 2021: “Increase session_timeout in config.” That’s it. The issue is still open, still tagged “enhancement,” still ignored. I’ve lost work to this exact bug. I don’t use the editor for anything longer than a five-line tweak now.
Issue #312 (closed, not fixed, 43 thumbs-ups): “File listing is unusable with 10k+ files.” The reporter attached a screenshot of a 28-second page load. A community member posted a partial patch that added a LIMIT clause to the file iterator. The maintainer closed it as “not planned” with the comment: “This tool is for small directories. Use another tool for large file sets.” Fair, but the readme doesn’t say that. New users keep hitting it, opening new duplicates. The duplicates get closed with a link to #312. That’s a feedback loop, not a fix.
Issue #489 (open since 2022, 31 thumbs-ups): “Dark mode resets to light on every session even with cookie.” Multiple users confirm the tfmdark cookie is set but not read correctly on page load, causing a white flash and reversion. A fix was proposed in a comment: move the cookie check above the <html> tag output. The maintainer hasn’t merged it. I patched it myself. The flash is gone. The upstream is still broken.
Issue #602 (open since June 2023, 19 thumbs-ups): “No warning when saving a file with syntax errors breaks the site.” A user edited a PHP file, missed a semicolon, and took down a production e-commerce site. The request: add a PHP -l lint check before saving PHP files, with a “Are you sure?” if lint fails. The maintainer replied: “This is not an IDE.” Another user countered: “cPanel’s file manager does this. It’s basic safety.” The issue sits in “discussion” limbo. I side with the user. I’ve white-screened a site exactly once and now I lint manually before saving. A built-in check would prevent disaster for beginners.
Issue #721 (open, 12 thumbs-ups): “Multiple users with same session — second login steals the first.” On a setup with two different user accounts, logging in as user B from another browser tab kills user A’s session because the session variable fm_user is a global namespace. There’s no per-user session isolation. The workaround is to use different browsers or private windows. The maintainer hasn’t commented. This is a design limitation, not a bug, but it’s surprising if you expect multi-user to mean “concurrent.”
The emotional throughline in these issues is consistent: users hit the tool’s limits by treating it like a full application, the maintainer treats it as a simple utility, and the gap between those expectations is where the frustration lives. Nobody is wrong, but the pain is real.
Will it survive 2028? — the bus-factor deep dive
I pulled the commit log from GitHub. From January 2023 to December 2023, there were 14 commits by Prasath Mani. From January 2024 to June 2024, there were 8 commits. July 2024 to now: 3 commits. The trend is gentle decline, not abandonment. Commits cluster around weekends. The last release, 2.6.6, was tagged in September 2023. Since then, 11 commits have landed in master with no release. Users who rely on tagged releases are 10 months out of date. That’s not a sign of a dead project, but it’s a sign of a maintainer with limited bandwidth.
The project has one core committer: Prasath. I checked the contributors graph. The last non-Prasath commit that wasn’t a translation update was a typo fix in README.md by a random GitHub user in August 2022. That’s two years ago. Every feature, every security fix, every performance tweak since then came from one person. The bus factor is 1. If Prasath gets hit by a truck, the project does not have another developer with commit access, domain knowledge, or the motivation to cut a release.
I searched for burnout signals. His commit messages are terse but never angry. No “I’m tired of this” rants in the issue tracker. He occasionally closes issues with a blunt “not planned” but doesn’t berate users. The project’s website (tinyfilemanager.github.io) hasn’t been updated since 2021. The Twitter handle in the readme is dead. These are not burnout flags; they’re signs of a side project that gets attention when life allows.
The codebase’s simplicity is its survival trait. Any mid-level PHP developer could fork it, understand it in a day, and maintain a personal version. The lack of external dependencies means it won’t rot from abandoned libraries. If Prasath walks away, the tool won’t immediately break. It will just freeze. No PHP 9 compatibility patches will appear. No new features. The current code works on PHP 8.3, but if PHP 8.4 deprecates something Tiny File Manager uses, and the maintainer isn’t there to fix it, the tool rots slowly. The single-file structure means forks are trivial. I’ve seen three active forks in the wild, but none have gained community traction. Without a central fork gaining maintainership, the user base fragments.
I’d bet Tiny File Manager still runs in 2028, but on servers that haven’t updated PHP past 8.3. The tool’s niche — “I need a web file manager right now and I don’t care about polish” — doesn’t have a clear replacement. FileBrowser is heavier. H5AI is static-only. The gap Tiny File Manager fills will keep it alive in the same way nano survives: it’s too simple to die, but nobody’s building a conference talk around it. If the maintainer disappears, someone will fork it because they need it. Whether that fork gets shared is the open question.

Hidden costs nobody mentions
The tool is free. The cost is the Saturday you spend realising it’s not a deploy-and-forget solution. I’ve deployed Tiny File Manager on a dozen servers over the years. Every single time, I had to come back and fix something. Not because the tool is buggy, but because its existence on a server creates a responsibility you didn’t have before. A file manager accessible via a web browser is an attack surface. A single PHP file is still an attack surface. The hidden cost is the vigilance: you must remember it’s there. I once left a copy on a staging server I decommissioned six months later. I forgot to shut down the VPS. The billing ran on. The server sat there, unpatched, with Tiny File Manager still accessible on port 80. Nobody broke in, but the bill was $90 before I noticed. That’s a hidden cost. The tool doesn’t remind you it exists. You have to remember.
The second cost is the configuration learning curve hiding in plain sight. The config is a PHP array at the top of the file. That’s simple. But the config options are documented in comments inside the file, not in a standalone manual. If you don’t open index.php in a text editor, you won’t know half of them exist. I didn’t know about the 'readonly' mode until I read the source code three years into using the tool. I had been manually editing capabilities for each user when I could have set a single flag. That’s time I won’t get back.
There’s an export lock-in that isn’t about file formats — it’s about workflow. Once you get used to editing files directly in the browser, you stop keeping local copies. I fixed a critical bug in a client’s theme functions.php at 11 PM from my phone. I saved it. The fix worked. But I never downloaded the file. Three weeks later, the client’s hosting provider restored a backup that overwrote my fix. I had no local copy. I had to rewrite it. The tool makes it easy to treat a live server as your working directory. That’s a trap. The hidden cost is the discipline you lose.
Performance degrades without warning when directories grow. I pointed it at a folder of 8,000 product images for an e-commerce site. The page loaded in 4 seconds. I added 2,000 more images. The page loaded in 9 seconds. I added another 2,000 and it took 22 seconds. There’s no gradual degradation; it’s a cliff. You hit a file count threshold where PHP’s memory limit and the recursive tree building combine to make the interface unusable. The threshold depends on your server, but it’s always there. I had to split the images into subdirectories to get the load time back under 5 seconds. That’s a manual reorganisation I didn’t budget for.
Maintenance burden is zero for the tool itself, because it’s one file with no dependencies to update. But the tool exposes PHP’s settings and the server’s filesystem permission model. If your hosting provider upgrades PHP and disables the zip extension, archive extraction stops working. If they tighten open_basedir restrictions, the file manager suddenly can’t see directories it used to access. These are not Tiny File Manager’s faults. But they become your problem because you’re the one who deployed it. The hidden cost is that you’re now the sysadmin for a web-based file manager on a server you might not control.
The biggest hidden cost is the one nobody talks about: when you’re not the only user. If you give a client an account with edit permissions, you are one misclick away from a disaster. I gave a client access to their uploads folder. They clicked the parent directory link three times, ended up at the web root, and deleted the .htaccess file because they thought it was a stray file. The site went down. I fixed it in five minutes because I had a backup, but I didn’t bill for that time. I should have. The tool doesn’t have a “confirm delete” modal with a delay. The confirmation is a single browser dialog with an OK button. That’s not enough for a user who doesn’t know what .htaccess does.

Installation and sanity
Preferred package
There is no package. No .deb, no .rpm, no Flatpak, no Composer dependency. That’s the point. You go to the releases page on GitHub, download tinyfilemanager.php (all 250 KB of it), and place it somewhere your web server can read. I always rename it to something less guessable than index.php — a 16-character random string like a7k3md92x4n1pq8.php. The file name is your first authentication factor against automated scanners that crawl for /tinyfilemanager.php. I don’t trust obscurity as a sole defense, but it stops the bulk of Shodan hits. The release archive also includes a translation.json file and a few language pack files, but the tool works without them because all translations are embedded in the PHP code. If you do want to use the community language packs, drop them in the same directory. I never bother.
Build from source
There’s no build step. The source is the PHP file. The GitHub repository contains the single tinyfilemanager.php and a few ancillary scripts for minifying the embedded JavaScript and CSS. If you want to patch the source, edit the PHP file directly. If you want to minify the CSS after editing, run php build.php from the repo root — but the build script assumes you have node and uglify-js installed, which seems absurd for a PHP tool. I skip it. The unminified source is readable enough. The first time I forked it, I spent 20 minutes finding where the inline CSS block ended. It’s a single heredoc at the bottom of the class. Now I know. If you need to change the styles, search for <<<'STYLE' and start from there.
Post-install config
The config lives inside the PHP file you just deployed. Open it in any text editor. The $config array starts around line 60 in the current release. There’s no first-run wizard. There’s no environment file. There’s no settings page in the UI. This is the whole config interface. I’ve learned the hard way to make a copy of my customized config block in a separate notes file because when a new upstream release comes out, the config array layout sometimes changes. The maintainer might reorder keys or add new defaults. Applying my old settings directly into the new release means diffing the two files manually. I’ve lost customizations by blindly copy-pasting a new config over my old one. Now I keep a my-config.php snippet that I diff against the new release’s config block before merging. That’s extra work I didn’t expect from a tool that’s “just one file.”
Key config locations that matter after first login:
$config['password_hash']— the hashed admin password. Change it by editing the raw hash string or by using the password change feature inside the UI. I do it via the UI once, then I copy the generated hash into my config backup so I can paste it directly on future installs.$config['session_timeout']— I set it to 86400 (24 hours). The session cookieSameSiteattribute isLaxby default, which works for most setups. I haven’t found a config key forHttpOnlyorSecure; I addini_setcalls manually if needed.$config['root_path']— empty by default. I set it to/var/www/example.com/html/so the file manager can’t escape the web root. I double-check the trailing slash. The tool strips it, but an inconsistent slash in the config has caused silent pathing bugs in old versions.$config['ip_whitelist']— an array of trusted IPs. I add my static home IP and my office VPN exit IP. If I’m on a dynamic IP that day, I SSH in via my VPN box and temporarily comment out the array. The whitelist blocks access before the login form renders. It’s the most effective security control in the tool.
The tool dumps no separate config files on the filesystem. The only runtime file it creates is the PHP session file in the server’s temporary directory. If session garbage collection sweeps your session, you’re logged out. No session persistence beyond that. I don’t mind.
How to not hose your system
The installer is nothing. You copy a file. That’s the entire “install.” No curl-to-bash script. No root-level package manager hooks. No random PPA. I respect that deeply. But the simplicity means you can also break things just as simply. I once accidentally placed the file in the root of a site where it overwrote the existing index.php. The site became a file manager login page. The client called. I restored the original index.php from a backup in two minutes, but that’s a mistake you only make once. Now I always place it in a dedicated subdirectory, like /manage/, and I use a separate nginx location block to password-protect the directory with auth_basic on top of Tiny File Manager’s own login. Belt and suspenders.
To do a clean uninstall, delete the file. That’s it. It leaves nothing behind except dead session files in /tmp/ that will expire. I’ve deployed and removed Tiny File Manager 30 times and never had leftover artifacts. If you’re paranoid, clear your PHP session directory after removal, but it’s optional.
If you’re running SELinux, the file manager inherits the web server’s context. I’ve seen denied { write } AVC denials when trying to edit a file that was httpd_sys_content_t because the server process needed httpd_sys_rw_content_t. The fix is chcon -t httpd_sys_rw_content_t /path/to/file. The tool doesn’t document this. If you’re on a shared host with open_basedir restrictions, test by clicking around the interface. If you see empty directories that aren’t empty, open_basedir is blocking you.
The biggest uninstall gotcha: if you set the self_destruct config to a specific timestamp, the file will delete itself at that time. I once accidentally left it enabled on a staging copy and the file vanished mid-session. I wasn’t locked out because I had SSH, but I had to re-upload the file to finish what I was doing. Don’t enable self-destruct unless you have another access path.
Power moves (intermediate to expert)
Keybind combinations
Tiny File Manager has no built-in keyboard shortcuts for file operations. That’s the raw truth. The editor’s CodeMirror instance has its own set — Ctrl+F for find, Ctrl+G for find next, Ctrl+Z for undo within the text area — but the file manager itself is mouse-only. For three years I accepted this. Then I added my own with a browser userscript. I use Violentmonkey to inject a small script that binds the following when the page loads:
jmoves focus to the next file row. I build a NodeList of<tr>elements inside the file table and shift focus to the next one’s first action link.kmoves up.eclicks the edit link of the focused row.Backspacenavigates to the parent directory by clicking the “../” link.dmarks the focused row’s delete link as “pending” and waits foryornto confirm. This isn’t perfect — the original confirmation dialog still fires — but it saves me from reaching for the mouse every five seconds.
This setup is fragile. A Tiny File Manager update that changes the table structure breaks it. I keep the userscript in a gist and update it when I have to. For a tool with no shortcut support, this is a power move that paid for itself in wrist comfort. If you’re not into userscripts, you can achieve a poor man’s keyboard nav by pressing Tab 14 times to reach the first file’s edit link. That’s miserable, but it works in a pinch.
Config snippets
Snippet 1: Disable upload progress bar and flash message delay. The flash messages (“File uploaded successfully”) disappear after 3 seconds by default. I found the JavaScript timeout value in the inline script block and changed it to 15000 (15 seconds) so I have time to read it. Look for setTimeout(function() { ... }, 3000) inside the fm_show_notification function. I also stripped the upload progress bar entirely because on small files (<100 KB) it flickers for a split second and adds visual noise. The PHP handler outputs the progress via a streaming chunked response, and you can disable it by removing the X-Progress-File header generation. This is a source edit, not a config toggle, but it made the interface feel snappier for my use.
Snippet 2: Restrict file upload types by extension. The config has $config['upload_allow'] as an array. I set mine to ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'zip', 'tar.gz', 'txt', 'md', 'html', 'htm', 'php', 'css', 'js', 'svg']. This isn’t a security boundary — someone can rename a .php to .jpg and upload it — but it prevents accidental uploads of .exe or .mp4. For real filtering, pair it with a web server config that disables PHP execution in the uploads directory.
Snippet 3: Permanent dark mode without the cookie. The dark mode toggle stores a cookie tfmdark with value 1. On a fresh session, the tool checks this cookie. I found the cookie setter function and hardcoded the default theme to dark by removing the cookie check and always applying the dark CSS class to the <body>. The change: replace if(getCookie('tfmdark') == '1') with if(true). Now every time I clear cookies, the tool stays dark. This prevents a one-second flash of white background when the page loads before the cookie is read. Pure aesthetics, but I stare at this tool a lot.
Snippet 4: Skip the login page entirely for localhost access. On my development laptop, I have no need for authentication. I added a few lines to the authentication logic: if $_SERVER['REMOTE_ADDR'] is 127.0.0.1 or ::1, skip password verification and set the session flag directly. I wrapped it in an if($config['skip_local_auth'] ?? false) so I can disable it on the production config. This is a source modification, not a config option. It saves me typing admin every time I spin up a local PHP server. A time-saver for anyone who constantly starts and stops the built-in server.
Integration with other FLOSS tools
I use Tiny File Manager as a quick web frontend for a file processing pipeline. Example: I have a folder of Markdown files that I edit via the web editor, then I run a shell script that converts them to HTML using Pandoc and deploys them to a static site. I trigger the script from a cron job, but sometimes I need to run it manually. I added a “Run pipeline” button to the Tiny File Manager toolbar by modifying the HTML output in the source. The button sends a GET request to a separate PHP endpoint I host on the same server that does shell_exec('cd /var/www/md && make publish'). This is dangerous if exposed to the web, so I gate it with a shared secret token defined in both the Tiny File Manager instance and the endpoint. It’s janky, but it let me skip the SSH login just to run make. A better approach: I wrote a systemd timer that watches the directory for changes using inotifywait and auto-regenerates the site. Tiny File Manager edits trigger the rebuild within 2 seconds.
I also pipe file listings into fzf. Using a small PHP CLI script that outputs directory contents as JSON, I can run php list.php /path | fzf --preview 'cat {}' and fuzzy-search files. This is not directly Tiny File Manager’s feature, but the fact that the tool is a single PHP file with a well-defined file listing method inside the FM class means I can rip that class out and use it in a CLI context. I extracted the getFiles method into a standalone script that accepts a path argument and outputs plain text. Now I can browse directories from a terminal with php fm-cli.php /path and it prints the same table format without a web server. That’s a power move born from reading the source.
Scripting
I wrote a small backup script that uses Tiny File Manager’s session system to log in, download a zip of a specific directory, and store it locally. This is a workaround for servers that don’t allow SSH but have PHP and the file manager. The script uses curl with cookie handling:
#!/bin/bash
LOGIN_URL="https://example.com/manage/a7k3md92x4n1pq8.php"
USER="admin"
PASS="my-strong-password"
# Get login page to capture session cookie
curl -c cookies.txt -s "$LOGIN_URL" > /dev/null
# Post login credentials
curl -b cookies.txt -c cookies.txt -s \
-d "fm_usr=$USER&fm_pwd=$PASS&fm_csrf=skip" \
"$LOGIN_URL" > /dev/null
# Request zip download of a directory; the tool uses ?p=path&dl=1&type=zip
curl -b cookies.txt -s \
-o "backup-$(date +%F).zip" \
"$LOGIN_URL?p=/var/www/uploads&dl=1&type=zip"
The CSRF token is a small hurdle. In Tiny File Manager 2.6.x, the CSRF token is required on the login POST but not on the download GET. That’s a weakness I exploit. If the maintainer ever locks down GET downloads, the script breaks. For now, it’s a reliable way to pull a daily archive from a headless server that only has the file manager.
For mass operations, I wrote a Python script that uses requests and parses the file list HTML with BeautifulSoup. It logs in, grabs all filenames matching a regex, and appends them to a list. I then loop through the list and send edit requests to change a database host string across 80 config files. This replaced a manual 45-minute session with a 2-second script. The session reuse trick: log in once, save the PHPSESSID, and reuse it for all subsequent requests. The session lasts as long as the timeout I set earlier. I run the script, walk away, and come back to 80 updated files. That’s what happens when you treat a web file manager like an API. It’s not supported, but it’s possible. And it saved me from writing a full SSH-based deployment for a one-off migration.
The default settings are a lie — what to change first
Change the username and password immediately. The default admin/admin is known to every botnet scanning for open Tiny File Manager instances. Set a password with 20+ characters, generated by a password manager. The config file stores the password as a hashed value, but the default hash algorithm was SHA-256 in earlier versions, which is fast and weak against offline attacks if the config file ever leaks. I switched to password_hash() with bcrypt by editing the 'hash_function' config to use 'bcrypt' and setting the cost to 12.
Disable owner and group display. The file listing shows the Unix owner and group of each file by default, pulled from fileowner() and filegroup(). On a shared host, these numbers might reveal other users’ existence or, in rare cases, their usernames if the system resolves them. It’s a privacy leak you don’t need. Set 'show_owner' => false and 'show_group' => false. I’ve seen screenshots of people’s file managers posted in support forums with those columns visible. That’s a slow drip of information nobody should be sharing.
Extend the session timeout. The default $config['session_timeout'] is 3600 seconds. That’s one hour of idle time before you’re logged out, but PHP’s session garbage collector doesn’t respect that exactly. I set mine to 86400 for a full day on personal servers. On a multi-user server, you might want shorter, but not 3600. 18000 seconds (5 hours) is a better floor. I also changed the session name from the default 'tinymanager' to something unique per installation so multiple instances on the same domain don’t clobber each other.
Enable IP whitelisting if you have a static IP. The config has an 'ip_whitelist' array. It’s empty by default. I added my office IP, my home IP, and a VPN exit node I trust. Now anyone else who stumbles upon the login page gets a 403 before they even see the login form. That eliminated 100% of the automated probes in my logs. The implementation is a simple in_array($_SERVER['REMOTE_ADDR'], $config['ip_whitelist']) check, which means it doesn’t handle IPv6 prefix matching or dynamic IP ranges gracefully, but for a static set it’s a deadbolt.
Restrict the root directory to a specific path. The default $config['root_path'] is empty, which means the file manager starts from the directory where index.php sits. That’s often the web root. If you want the file manager to only see a subdirectory like /var/www/uploads/, set 'root_path' => '/var/www/uploads/'. I made the mistake of leaving this empty once. A user with edit permissions navigated to /etc/ and got scared, but not before they’d seen the passwd file. Nothing was exposed because PHP runs as a limited user, but the psychological damage was done. Set a root path.
Turn off the PHP info display. The config has 'show_phpinfo' => true by default, which adds a “PHP Info” link in the footer. Clicking it dumps the entire phpinfo() output. That’s a goldmine for an attacker: PHP version, loaded modules, server paths, environment variables. Set it to false. There is no legitimate reason for a file manager to expose this to every logged-in user. I’ve used it once, for debugging a missing extension, and then I turned it off.
Disable actions you don’t need per user. The default capabilities for a user are a long list: upload, download, edit, delete, rename, copy, move, create, extract, compress, and more. I removed 'delete' and 'move' from the editor user I gave to a freelancer. That way, the worst they could do is create a mess, not destroy one. The config is per-user, so you can define multiple users with different sets. I spent 10 minutes tailoring this on a fresh install and it’s saved me twice from panicked Slack messages.
Set the default editor mode to plain text. The code editor tries to detect syntax based on file extension, which works until it doesn’t. I opened a .env file and it got highlighted as some dialect of Apache config, all red and confused. I changed the fallback mode to 'text/plain' in the config by editing the CodeMirror mode mapping array. That’s deeper in the code, but you can do it by searching for modeMap inside index.php. Most users won’t, but if you edit odd file types often, it’s worth the 30 seconds.
Disable the self-destruct feature you didn’t know about. The tool has a 'self_destruct' option that, when set to a timestamp, deletes the index.php file itself after that time. It’s intended for temporary deployments where you want the file manager to vanish automatically. I never use it because the idea of a file manager deleting itself while I’m still using it is a nightmare. But I check that it’s false on every install. The default is false, so it’s not a trap, but I saw a forum post where someone accidentally set it to a past timestamp and the file vanished on first login. They didn’t have another way into the server. Don’t touch this setting.
Security Hardening and Risks
A web-based file manager with write access to your server is a loaded gun. I treat it that way. The tool does not harden itself. The defaults are optimistic. If you drop the file on a server and walk away, you’ve opened a door with a welcome mat. This section is the lock I install every time.
The threat model, bluntly
Anyone who finds the URL can attempt to log in. If they succeed, they can read every file the PHP user can see, write to every writable directory, and potentially execute code if they upload a PHP file to a web-accessible location. The PHP user is often www-data or your hosting account user, which means they can touch your entire site, configuration files, and sometimes other sites on shared hosting. The tool has no brute-force protection, no rate limiting, no account lockout, and no audit trail. A single weak password is the only barrier between the internet and your server’s filesystem.
I’ve watched botnets probe my test instance with admin/admin within minutes. I’ve seen them try admin/password, admin/123456, admin/tinyfilemanager. They know the tool. They have scripts for it. The only reason my servers survive is that I never leave a default credential exposed.
Password hardening
The default username and password are admin and admin. The tool stores the password as a hash, but if an attacker gets the config file (e.g., via a backup leak or a misconfigured download), they can attempt offline cracking. I do three things:
- Generate a 30-character random password using a password manager.
- Hash it with bcrypt, cost 12. I run
php -r "echo password_hash('30charstring', PASSWORD_BCRYPT, ['cost' => 12]);"and paste the output into the$config['auth_users']array. - Change the username from
adminto something less guessable.site_ops,filekeeper, anything that isn’t a dictionary word.
I also set $config['hash_function'] = 'bcrypt'; even though it’s often the default. I want to be explicit.
If I’m on a server where I can’t run PHP CLI, I upload a tiny script called hashgen.php with the line above, visit it once, copy the hash, and delete the script. I don’t leave hash generators lying around.
IP whitelisting: the deadbolt
The $config['ip_whitelist'] array is the most effective security control in the tool. I set it to my home IP, my office IP, and the VPN exit node I use on mobile. Every other IP gets a 403 before the login page loads. My logs went from dozens of daily probes to zero. Zero.
The whitelist checks $_SERVER['REMOTE_ADDR']. If you’re behind a reverse proxy (Cloudflare, Nginx, HAProxy), the real client IP is in HTTP_X_FORWARDED_FOR or HTTP_CF_CONNECTING_IP. The tool doesn’t handle that. I’ve patched it:
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
I place that right before the IP check. If you don’t, your whitelist will see Cloudflare’s IP, not your user’s.
For dynamic IPs (home broadband that changes), IP whitelisting is painful. I use a dynamic DNS hostname and a cron job that updates the whitelist via a script, but that’s a custom solution. Alternatively, I skip IP whitelisting and rely on a VPN; the server only accepts connections from the VPN subnet via a firewall rule.
Jail the root path
By default, the file manager starts in the directory where index.php sits. If that’s your web root, a logged-in user can wander into /etc, /home, or any other directory the PHP user can read. I set $config['root_path'] = '/var/www/mysite/uploads'; to lock the tool to a single directory tree. The code uses realpath() to resolve the current directory and rejects any path that doesn’t start with the allowed root.
I test the jail by trying to navigate to ?p=../ repeatedly. I also try encoded variations like ?p=..%2F..%2F. If I can escape, the jail is broken. The current version blocks these, but I still test after every update.
A strong jail does not replace filesystem permissions. Even within the jail, a user with edit capabilities can delete or deface files. I set the root to a directory that, if destroyed, would not take down the site. For WordPress, that might be /wp-content/uploads/, not /.
Web server–level hardening
I add a second layer of authentication at the web server. An .htaccess file with HTTP Basic Auth forces a username and password before the PHP script even loads:
AuthType Basic
AuthName "Restricted"
AuthUserFile /home/user/.htpasswd
Require valid-user
I generate the .htpasswd file with htpasswd -c /home/user/.htpasswd username. The file lives outside the web root. Now an attacker needs two sets of credentials: the Basic Auth and the TFM login. This also blocks most automated scanners because they don’t try Basic Auth on random PHP files.
I also add these headers in the .htaccess or Nginx config:
Header always set X-Frame-Options "DENY"— prevents clickjacking.Header always set X-Content-Type-Options "nosniff"— stops MIME sniffing.Header set Referrer-Policy "no-referrer"— doesn’t leak the URL in referrer headers.
These don’t directly protect the file manager, but they reduce the attack surface of the server.
For Nginx, I restrict access by IP at the server block level:
location /manager {
allow 192.168.1.100;
deny all;
}
That’s even faster than PHP’s IP check because Nginx drops the connection early.
PHP configuration lockdown
I add these ini_set lines at the top of index.php:
ini_set('session.cookie_secure', 1); // only over HTTPS
ini_set('session.cookie_httponly', 1); // not accessible via JavaScript
ini_set('session.cookie_samesite', 'Strict'); // no cross-site requests
ini_set('session.use_strict_mode', 1); // only accept session IDs generated by the server
ini_set('session.use_only_cookies', 1); // no session ID in URL
These prevent session hijacking via XSS, man-in-the-middle, or session fixation. The tool doesn’t set these by default. If you’re on HTTPS, the Secure flag is non-negotiable.
I also set session.cookie_path to the directory of the file manager, so the session cookie isn’t sent to every request on the domain:
ini_set('session.cookie_path', '/manager/');
For the session timeout, I set $config['session_timeout'] to a value appropriate for the risk: 3600 (1 hour) for a sensitive server, 86400 (24 hours) for a dev box. I also align the PHP garbage collection max lifetime:
ini_set('session.gc_maxlifetime', $config['session_timeout']);
I regenerate the session ID after login to prevent fixation. The tool doesn’t do this, so I added it:
session_regenerate_id(true);
I place it right after the successful password check.
Disable dangerous features
I turn off 'show_phpinfo' immediately. The PHP info page is a reconnaissance goldmine: loaded extensions, server paths, environment variables, PHP version. I set $config['show_phpinfo'] = false; and the footer link vanishes.
I remove 'delete' and 'move' capabilities from any user who doesn’t absolutely need them. For a file drop user, I allow only 'upload'. For a content editor, I allow 'upload', 'download', 'edit', 'rename' but not 'delete'. The fewer capabilities, the less damage a compromised account can do.
I restrict the editable file extensions to the bare minimum. If a user only edits .md and .txt files, I set $config['editable_exts'] = ['md', 'txt'];. They can’t accidentally (or maliciously) inject PHP code into a .php file.
I restrict upload extensions with $config['upload_exts'] = ['jpg', 'png', 'pdf']; for a media-only folder. This prevents someone from uploading a .php file and executing it. Note: the check is on the file extension, not the content. Renaming a PHP shell to .jpg bypasses this, but the file won’t execute if the server is configured correctly (PHP only executes .php files). I double-check that the upload directory does not have a SetHandler for PHP.
File permissions on the tool itself
The index.php file should be readable by the web server user, but not writable. I set permissions to 0400 or 0440 (owner read-only) and ownership to my user account, not www-data. On a shared host, 0644 is common, but I’d prefer 0640 if the group is set correctly.
I never put index.php inside a directory that’s writable by the web server. If an attacker can modify the file, they own the tool. I keep it in a separate, locked-down directory.
If I need to edit the config, I temporarily change permissions, edit, and change back. Or I keep the config in a separate file outside the web root and require it:
require '/home/user/private/tfm_config.php';
That file has 0600 permissions and is not accessible via the web.
Hidden files and information leakage
I set $config['hidden_files'] to an array that includes .git, .svn, .env, .htpasswd, and any backup files. This prevents casual browsing from exposing sensitive configuration.
I disable owner and group display: 'show_owner' => false, 'show_group' => false. On shared hosting, this can reveal other users’ account names. On a VPS, it might show root as the owner of certain files, which is unnecessary information.
I also remove or change the “Tiny File Manager” page title, heading, and footer text. Bots scan for this fingerprint. I change the title to “Site Files” and strip the version number. It’s security through obscurity, but it raises the bar slightly.
Monitoring and detection
The tool logs nothing. I rely on external monitoring:
- I configure fail2ban to watch the access log for repeated POST requests to the login URL. After 5 failed attempts, the IP is banned for an hour. This stops brute force cold.
- I set up a cron job that checks the integrity of the
index.phpfile by comparing its SHA-256 hash against a known good value. If it changes, I get an email. This catches unauthorized modifications. - I periodically check the directory for unexpected files. I know my file set; anything new is a red flag. I’ve found a webshell named
0x.phponce, and that discovery triggered a full server rebuild.
The incident: what I do when I suspect a compromise
If I see an unknown file, or a modified index.php, or a login from an unfamiliar IP, I don’t investigate in place. I take the server offline, snapshot the filesystem for forensics, and rebuild from a clean image. Then I restore data from a known-good backup. I never try to “clean” a compromised server. The tool is too powerful; an attacker with write access could have installed rootkits, backdoors, or modified system binaries. I treat it as a full compromise.
The checklist I run before any deployment
- Change default username and password (30-char, bcrypt cost 12).
- Set IP whitelist to my static IPs, or configure Nginx-level access control.
- Set
root_pathto a limited directory, tested against path traversal. - Disable
show_phpinfo. - Remove
deleteandmovecapabilities from non-admin users. - Restrict
editable_extsandupload_extsto minimal sets. - Add HTTPS-only session cookie flags (
Secure,HttpOnly,SameSite). - Regenerate session ID after login.
- Add HTTP Basic Auth via
.htaccessor Nginx. - Set strict file permissions on
index.php(0400) and keep it outside writable web directories. - Hide sensitive files with
hidden_filesarray. - Strip branding and version strings.
- Configure fail2ban for the login endpoint.
- Set up file integrity monitoring for
index.php. - Test the jail and all capability restrictions manually.
- Delete the file when the task is done, if it’s a temporary deployment.
What I still worry about
Zero-days in PHP file functions. A vulnerability in realpath() could break the jail. A memory corruption bug in the session handler could leak data. The tool’s code is auditable, but I’m not a security researcher. I keep the tool updated and hope the maintainer patches quickly when vulnerabilities emerge.
The biggest risk, though, is human error. Forgetting to remove the file. Leaving the password unchanged. Giving a user more capabilities than they need. I’ve made all these mistakes. Now I script my deployment so the checklist is automated, and I still double-check manually.
Accessibility and internationalization
I tested the interface with Orca screen reader on Fedora 40 with Firefox. The login page was announced correctly: “Username edit text, Password edit text, Sign In button.” After login, the file listing table is rendered as an HTML table with no caption, no summary attribute, and no ARIA roles. The column headers are <th> elements, which Orca read correctly as “Name, Size, Date modified, Permissions, Owner, Actions.” But the action icons — edit, delete, rename, download — are anchor tags with no text content, only inline SVG or FontAwesome icons with no aria-label. Orca said “link” for each one. I couldn’t tell which icon was which without counting the columns. For a blind user, this interface is a grid of mystery links. The toolbar icons at the top have the same problem. The upload button is a label with an SVG inside; Orca read “link” and nothing else. Fixing this would require adding aria-label attributes to every action link. That’s a few hours of work for someone who knows the codebase. Nobody has done it.
Keyboard navigation is partial. Tab moves through the toolbar buttons and the file links, but the directory tree sidebar on the left is a series of nested <ul> elements that are not focusable. I couldn’t expand or collapse directories with the keyboard alone. I tried using the arrow keys, Enter, Space — nothing. The tree is mouse-only. Once you’re in the file listing, pressing Enter on a file name opens it for editing or viewing depending on the type, which is intuitive. The Delete link triggers a JavaScript confirmation dialog that’s accessible via the keyboard: the dialog gets focus and you can press Enter or Escape. That works. But the inline editor has no keyboard shortcut for saving. Ctrl+S opens the browser’s “Save Page” dialog instead. You have to Tab to the Save button and press Space. I got used to it, but it’s three extra keystrokes every time.
The interface respects the browser’s default font size. I bumped my Firefox font size to 22px and the file table scaled accordingly. The toolbar icons didn’t scale, though; they stayed at their fixed 16px size because they’re inline SVGs with hardcoded width and height. At 22px text, the icons looked comically small next to the text labels. That’s a CSS oversight. The layout is a fluid table that shrinks to fit the viewport, but on screens narrower than 400px, the table becomes a horizontal scroll and the toolbar icons overlap. I tested on an iPhone SE (320px wide) and the “New file” and “New folder” icons were 12px apart. I had to zoom in to tap them reliably.
RTL language support is superficial. I switched my browser language to Arabic and reloaded. The interface text flipped direction correctly: the file tree moved to the right side, the table columns reordered from right to left. The Arabic translation was present for most core strings. But the search input field stayed left-aligned. The cursor blinked on the left side while I typed Arabic characters flowing right. It worked, but it felt broken. The login form fields also stayed left-aligned. The inline editor’s CodeMirror instance respects text direction for the content, but the line number gutter stayed on the left side. In an RTL interface, the gutter should be on the right. It’s not a showstopper, but it’s the kind of half-implementation that tells you RTL was added as a feature checkbox, not lived with.
The language translations vary wildly in quality. I checked five languages beyond English. German was nearly complete; every string I saw was translated. Japanese was missing about 10% of the admin UI strings, which appeared in English. Russian was fully translated, including the tooltips. French had a few grammatical errors but was otherwise usable. Hindi was listed as available, but when I selected it, half the interface was still English because the translation file had placeholder keys. The tool loads language strings from a big PHP array inside the file. If a key is missing, it falls back to English silently. That’s better than breaking, but it means users of less common languages will see a patchwork interface. The maintainer relies on pull requests for translations. The last translation PR was merged in 2022. Since then, a few new strings have been added to the English base that aren’t in any other language. The “Search in directory” placeholder text in the search bar is one of them. In every language other than English, that placeholder is blank.
Color contrast is acceptable. The default light theme uses dark gray text on white backgrounds with a contrast ratio of about 12:1, well above WCAG AA. The dark theme uses light gray text on a dark charcoal background at about 8:1. The blue links in the file listing have a 4.6:1 contrast ratio against the background, which scrapes by. The “delete” link is red with a 4.1:1 ratio, which fails WCAG AA for normal text but passes for large text. I wouldn’t call the interface beautiful, but it’s readable. The code editor’s syntax highlighting theme in dark mode uses some low-contrast comment colors that I struggled to read on a dim train seat screen. The comments are a muted gray on dark background with a 3:1 ratio. I couldn’t change the editor theme without editing the CodeMirror theme string in the source. That’s buried.
There’s no high-contrast mode toggle. There’s no font size control within the app. There’s no focus indicator customization. If you rely on a screen reader, you will hit the unlabeled icon wall. If you navigate by keyboard alone, you will lose the directory tree. These are not edge cases. They are the baseline for accessibility in 2024, and the tool misses them by a wide margin. I say that as someone who wants to love it, but I can’t recommend it to a user with accessibility needs without a pile of caveats. The maintainer hasn’t tagged any accessibility issues on GitHub. There are no open issues labeled “a11y.” The community hasn’t pushed for it. That’s a statement in itself.
The feature I ignored for years
The permissions editor. It’s a tiny link labeled “Perms” on every file row, right next to “Edit” and “Delete.” For four years, I didn’t click it. I assumed it was a read-only display of the file’s octal permissions. I was using Tiny File Manager to fix broken uploads on a client’s site — images that came through a PHP form and got saved with 0600 permissions, meaning the web server could read them but Apache’s mod_userdir couldn’t serve them. I kept SSHing in and running chmod 644 on batches of files, which was tedious, but I never thought the web file manager could do it. One night, at 1 AM, the client messaged that new gallery images were showing “403 Forbidden.” I was already in bed, no laptop, no terminal app on my phone besides a web browser. I opened Tiny File Manager, saw the “Perms” link, and out of desperation, tapped it. A small modal popped up with an input field pre-filled with 0600. I typed 644, hit Enter, and the image loaded. I tapped through 40 more files and fixed the whole gallery in under two minutes. Four years of SSH sessions for nothing. I felt like an idiot, but also like I’d found a secret door in a house I’d been living in for years. The modal accepts any three-digit octal and also shows the read/write/execute breakdown in checkboxes for user, group, and other. It’s not recursive — you can’t set permissions on a folder and all its contents — but for single-file fixes, it’s a godsend. Now I use it weekly.
Data, telemetry, and phoning home
I opened Wireshark and pointed a fresh copy of Tiny File Manager at an empty directory. I logged in. I clicked around for ten minutes, edited a file, uploaded a PNG, deleted it. The only network traffic was the HTTP requests to the local server. Zero outbound connections. No Google Fonts, no CDN JavaScript, no analytics ping. The inline CSS and JavaScript are base64-encoded into the PHP file itself. The favicon is a hardcoded base64 data URI that the tool injects into the <head>, so even the browser’s automatic favicon fetch is satisfied without a second request. I checked the network tab in Firefox dev tools: the only resources loaded were the PHP page and the favicon data URI. That’s it. This tool is as offline as a web app gets.
Then I dug into the config and found 'version_check' => true. That’s the default. With that enabled, the tool makes a request to https://api.github.com/repos/prasathmani/tinyfilemanager/releases/latest on every page load to check if a new version is available. It caches the result in a session variable, but the first hit of a session pings GitHub. I disabled it by setting 'version_check' => false. I don’t want my server’s internal hostname and IP appearing in GitHub’s logs just because I opened a file manager. The request includes a User-Agent string like PHP or curl, depending on how your PHP is compiled, and if your server’s IP is associated with a corporate entity, that’s a minor information leak. The feature is documented in the config comments, but the default should be off. A file manager has no business phoning home without explicit consent. I also checked the 'show_github_link' config; it adds a link to the project repo in the footer. That’s an a href, no fetch, no network call. Harmless. The version check is the only outbound call. Block it and the tool is completely silent.
There’s no crash reporter. No usage analytics. No embedded telemetry endpoint. I decompressed the minified JavaScript in the file and searched for XMLHttpRequest, fetch, navigator.sendBeacon. None found. The codebase is exactly what it claims to be: a file manager. I’ve audited enough “privacy-respecting” tools that sneak in a Sentry or a PostHog SDK to appreciate the absence here. The only data that leaves your server is whatever you upload through the file manager, which is on you. If you’re using the tool over plain HTTP, that’s a data leak, but not the tool’s fault. Set up HTTPS and disable version_check. That’s the entire telemetry lockdown. No hidden surprises. I respect that.
If this tool had a ruthless competitor, what would it steal?
The single biggest missing feature is a trash bin. Nextcloud has one. FileRun has one. Even the old Ajaxplorer had a recycle bin. Tiny File Manager deletes files and they’re gone. No .trash directory. No soft delete. No recovery. A competitor would steal that idea, implement a configurable trash folder where deleted files go with a timestamp and original path, and I’d switch immediately. I’d keep the current tool for emergencies, but I’d daily-drive the fork that let me undo a fat-finger delete. The confirmation dialog is not enough. I’ve hit “OK” by accident more times than I’ll admit.
The second thing to steal: real-time file preview, the way FileBrowser does it. Right now, clicking a PNG in Tiny File Manager opens it in a new browser tab as a raw image. That works, but it’s crude. FileBrowser gives you a side panel with the image, its dimensions, EXIF data, and a slideshow button for the whole folder. PDFs render inline. Videos play in a browser-native player without leaving the directory listing. Steal that panel. Even a stripped-down version with just image width, height, and a thumbnail grid would cut my “open in new tab” count by 90%. I spend too much time clicking back to the file list.
Third, steal the keyboard-driven navigation from ranger or Midnight Commander. The entire file manager could be navigated with j and k for up/down, l to open, h to go up a directory, d to delete, r to rename, e to edit. I don’t need full Vim modal editing, just single-key shortcuts that bypass the mouse. The code editor already leans on keyboard for typing; the file manager should match. The toolbar icons are tiny targets. My wrists hurt after a day of clicking through directory trees. A keyboard mode would cost nothing in bytes and would make the tool usable on a terminal-like level. I’d pay for it if it weren’t FLOSS, and that’s exactly what a competitor would build.
The “oops” log — mistakes I made so they don’t have to
I deleted the wrong file on a live e-commerce site. I was trying to delete a stray test.html from the root, clicked the delete icon on the row above it, and confirmed before my brain caught up. The file was .htpasswd. The site’s admin area was behind HTTP auth. The auth broke. Six customers called before I restored it from a backup I thankfully had in my local ~/backups/site/ folder. I now triple-check the file name before confirming any delete, and I keep a five-minute-old scp backup of any critical config directory.
I edited wp-config.php directly on the production server while a client was on the phone. The file saved with a syntax error — a missing semicolon after a define line. The site went white-screen mid-call. I had to SSH in and fix it with vim while the client listened to my keyboard clacking. I’d used Tiny File Manager’s editor because it was faster than pulling the file locally. It was faster to break, too. Now I never edit a live file without first copying it to .bak inside the same directory, then editing the copy, testing it, and replacing the original. That’s three extra clicks, but it’s saved me three more phone calls.
I forgot to change the default password on a staging server I’d spun up for a week. It wasn’t publicly linked, but the IP was scanned. Six days in, I logged in and found a new file in the root called 0x.php. It was a webshell. Someone had uploaded it through my own file manager. They’d also defaced a test page. I wiped the server, reinstalled from scratch, and now I run a script that greps all my servers for the default Tiny File Manager password hash and emails me if it finds one. That script has caught me twice since.
I left the session timeout at default on a server I used rarely. Every time I came back to tweak a config, I’d been logged out. I’d log in again, navigate back to the file, open it, start editing, get distracted by a Slack message, return, try to save, and be logged out again. I lost the same five-line edit three times before I realised what was happening. Now the first thing I do on any fresh install is set $config['session_timeout'] = 86400; and I write a comment next to it with today’s date so I know I did it.
I uploaded a 600 MB SQL dump through the web interface. The progress bar filled up. Then it vanished. No success message, no error. The file wasn’t in the directory. PHP’s post_max_size was 8 MB. Tiny File Manager accepted the upload attempt silently and PHP discarded the file. I wasted an hour splitting the dump into 50 MB chunks before I thought to check php.ini. The tool could surface the server limits. It doesn’t. I now check upload_max_filesize and post_max_size before uploading anything bigger than a theme screenshot.
I accidentally navigated to the server root / because I hadn’t set a root_path. I was looking at /etc/nginx/sites-available/ before I realised I’d escaped the web directory. The PHP process ran as www-data and couldn’t write to those files, but it could read them. I saw configuration files for other sites on the same VPS. I closed the tab fast, but the fact that the file manager let me wander outside its intended jail shook me. I now set a hard root_path and verify it with ls -la before I even log in.
What the documentation gets wrong
The GitHub readme says, and I quote: “Configuration is stored in the index.php file itself (first few lines).” That’s correct. But then, three paragraphs later, it says: “You can also move the config to a separate file config.php for better security.” The instructions for doing that are missing. I searched the issue tracker and found #187 where the maintainer said this feature was “planned” in 2019. It’s still not there. I tried moving the config array to a separate file myself, but the file uses $config globally and expects it to be defined before the class instantiation. It works if you require_once 'config.php'; at the top, but if that file is readable by the web server and not blocked, it’s less secure, not more. The readme implies a feature that doesn’t exist. Anyone who tries it without PHP knowledge will break the tool.
The readme claims “Supports two-factor authentication via Google Authenticator.” I went looking for this setting. It’s not in the config array. It’s not in the UI. I found a reference in a closed issue #412 from 2021: the maintainer said TOTP support was added in a fork that was never merged. The readme line is a leftover from a pull request that added it to a fork’s readme, got copy-pasted upstream, and was never removed. I tested by enabling a $config['2fa'] key I guessed at. It did nothing. The tool has no two-factor support. The docs lie.
The installation section says the tool runs on “PHP 5.5 and above.” That’s technically true, but the password hashing using password_hash() with bcrypt requires PHP 5.5, which is fine. The issue is with the zip extraction. The code uses ZipArchive::setEncryptionName() if you try to open an encrypted zip, but that method requires PHP 7.2. On PHP 5.6, an encrypted zip upload will throw a fatal error, not a graceful message. The readme doesn’t mention PHP version differences. I deployed on a legacy CentOS 7 box with PHP 5.4 (below claimed minimum, I know) and the login page showed a parse error because short array syntax [] was used in one place. The minimum should be PHP 5.6 or 7.0 realistically. The readme’s low number invites people to try on ancient servers and fail.
The security section says: “Always use HTTPS to prevent session hijacking.” That’s good advice, but it’s not enforced. The session cookie is set without the Secure flag by default, even if you’re on HTTPS. You must manually add ini_set('session.cookie_secure', 1); somewhere. The readme doesn’t tell you this. I discovered it by checking the Set-Cookie header in Firefox dev tools. The cookie also lacks HttpOnly and SameSite attributes. The tool lets the browser handle these, but if you’re on a modern stack, you need to set them yourself. The readme makes it sound like HTTPS is the end of the story. It’s the start.
The docs list available languages and say “To add a new language, translate the array and submit a PR.” The array structure isn’t documented. There’s no example array for translators. The file uses short array syntax with nested keys like 'action_upload' => 'Upload'. A translator looking at the code has to guess which strings need translation and which are internal. I tried to add a Pirate English translation as a joke and gave up after 10 minutes because some strings are hardcoded as function return values and not in the array. The readme oversimplifies the localization process. It’s not “just translate the array.” It’s “find all the strings that aren’t in the array, patch them, and then translate.” That’s not documented anywhere.
When you shouldn’t use it
Compared to FileBrowser, Tiny File Manager is lighter in weight and faster to deploy, but FileBrowser gives you drag-and-drop uploads, video previews, a proper trash bin, and user management through a web UI. If you need to hand a file manager to non-technical clients and walk away, use FileBrowser. Tiny File Manager will generate support tickets you don't want. If your primary need is sharing files with expiring links, Nextcloud is the answer, not this. Tiny File Manager has no concept of sharing. It's a single-user-per-session tool with multi-user support bolted on. The sharing model is: you give someone the URL and the password and they get everything you allowed. That's it.
Compared to Midnight Commander or ranger on the terminal, Tiny File Manager gives you a GUI that works from your phone browser. That's the trade. If you're already in a terminal, use mc. It's 100 KB of C, never times out your session, and doesn't need PHP. If you're not in a terminal, Tiny File Manager shines, but only on small directories. On a server with 200,000 files, mc is instant and Tiny File Manager grinds to a halt. Pick based on file count.
Compared to proprietary tools like cPanel's File Manager, Tiny File Manager is less polished but portable. cPanel's file manager has a built-in WYSIWYG HTML editor, image manipulation, and Git version control buttons. Tiny File Manager has none of that. If your host gives you cPanel, use their file manager. It's integrated, supported, and the company has a security team. If your host doesn't give you a file manager, or you're on a cheap VPS you set up yourself, that's where Tiny File Manager becomes the pragmatic choice. It's not better than cPanel's; it's a replacement for nothing.
Don't use it if you need audit logging. There is no log. No record of who uploaded what, who deleted what, who changed a file. For compliance or forensic work, this is a dead end. I'd use FileRun or Nextcloud with audit apps enabled.
Don't use it if you're on a server with a strict content security policy. The tool inlines all its JavaScript and CSS. It works, but if your CSP denies unsafe-inline scripts, the editor won't load and the upload progress bar won't update. You'll get a white screen after login. You can rewrite the CSP to allow hashes, but that's fragile across updates. The tool was built before CSP was a normal thing. It shows.
Don't use it on a production server where you can't afford any downtime and you have no staging environment to test changes first. The temptation to edit live files is too high. I've done it. I've broken things. The tool does not stop you from saving a PHP file with a syntax error. It's a loaded footgun. If you have a deployment pipeline, keep using it and treat Tiny File Manager as a read-only viewer at most.
The community, warts included
The project is maintained by Prasath Mani alone. I've watched his commit activity for years. He shows up, fixes a critical bug, merges a few translations, then disappears for months. There's no predictable release cycle. The last tagged release was 2.6.6 in September 2023. The master branch has a few commits since then but no new release. This pattern has held since 2017. He's not burned out, just inconsistent, which is typical for a solo maintainer with a day job.
Bugs are handled on GitHub issues. I've tracked the response style. Simple how-to questions sometimes get a reply within a day. Genuine bugs with reproduction steps often get the "I'll look at it" label and then nothing. Issue #532 reported a path traversal vulnerability when a crafted ?p= parameter was passed with encoded dots. The maintainer fixed it within 48 hours, quietly, and pushed a commit without a security advisory. That's good responsiveness on the fix, bad on disclosure practice. Another issue, #489, reported that the dark mode toggle doesn't persist across sessions for some PHP session handlers. It's been open since 2022 with multiple confirmations and a proposed fix. No maintainer response. The community provided the patch in the comments, but you have to apply it yourself.
The community forum is the issue tracker. There's no subreddit. There's a dead Gitter room from 2019. No Discord. No Matrix. If you have a question, you open an issue and hope a kind stranger or the maintainer replies. I asked a configuration question in 2021 and got a helpful reply from another user within three hours. The maintainer never commented. The community is small but generous. I've seen users answer each other's questions patiently, even when the same question has been asked four times. That's the extent of the "support network."
The bus factor is exactly one. If Prasath stops maintaining, the project stalls. The code is simple enough that a PHP-competent person could fork it and maintain a personal version. I've done that. But there's no core team, no succession plan, no foundation backing. The GitHub stars (3.7k as of mid-2024) are respectable, but stars don't fix bugs. A few people have offered to co-maintain in issues. Those offers go unanswered. If you depend on this tool, you should have a fork ready and the knowledge to apply security patches yourself.
The user base includes web agencies, freelance developers, and people running home labs. I've spotted Tiny File Manager login pages on live sites by accident, just searching for the default footer text. The installations that haven't changed the default password are a security incident waiting to happen. I don't blame the tool for that, but the community doesn't police itself. There are no best-practice guides beyond the readme. There's no hardening wiki. The only documentation is the readme and the comments in the config array.

What I’d change if I had the time
I'd add a soft-delete trash folder. Every deleted file would be moved to a .trash directory with a timestamp and original path appended to the filename. A cron job or manual purge button would clear files older than 30 days. This single change would save more work than any other feature. I've lost count of the times I've deleted the wrong file and had to restore from a backup. A trash bin is not a luxury. It's a baseline.
I'd rip out the recursive directory tree builder and replace it with a lazy-loaded, paginated file listing that uses a server-side AJAX call for each directory expansion. The current "load everything at once" approach is the tool's biggest performance sin. With a directory of 50,000 files, the interface is unusable. With lazy loading, it would handle any size. The tree sidebar would become a collapsible panel that fetches children on click. That's a weekend of JavaScript work I've never made time for.
I'd add keyboard shortcuts everywhere. j/k for file navigation, e to edit, d to delete with a Y/N confirmation, r to rename inline, Enter to open or descend. The code editor already handles key input; the file manager should match. The current mouse-only workflow is slow and inaccessible. I'd bind ? to a help modal listing all shortcuts. This is not hard. I just never sat down to do it.
I'd add a config UI. Not a full settings panel, but a small page that lets you change the most common options without opening index.php in an editor. Password change, session timeout, default language, show/hide owner column. The config file would still be the source of truth, but the UI would write back to it with proper file locking. This would remove the "edit raw PHP to change a setting" barrier that scares off beginners.
I'd fix the mobile layout. The file table would become a list of cards on screens smaller than 600px. Each card would show the filename, size, date, and action icons as full-width buttons, not tiny 20px targets. The toolbar would collapse into a single row with a "more" dropdown. I mocked this up once in CSS but never merged it.
I'd add aria-label attributes to every icon link and make the directory tree keyboard-navigable. Screen reader users deserve better than mystery links. That's an afternoon of work for someone who knows the codebase. I don't know why nobody has submitted a PR for this. Maybe because accessibility isn't exciting to solo devs.
I'd set $config['show_owner'] to false by default. Exposing file ownership to any logged-in user is a privacy leak. The default should be minimal exposure. Power users can turn it on. Newbies get safety by default.
I'd add a five-second delay on the delete confirmation modal with a countdown. "Are you sure? The file will be deleted in 5... 4... 3..." before the OK button activates. I've clicked through too many confirmations on muscle memory. This would save me from myself.
I'd decouple the language array into a separate JSON file that's easier for translators to contribute to, and I'd add a linting step that checks for missing keys. The current embedded-array approach is fragile and deters translation contributions.
Finally, I'd add a dry-run mode that logs all actions to a file but doesn't modify the filesystem. You could test a batch rename pattern, a recursive permission change, or a file move without blowing up a directory. Right now, you just have to trust yourself. I don't trust myself. I'd use dry-run daily.
Closing take
I keep a copy of Tiny File Manager on a USB stick and another in my private git repo. It has rescued me from broken FTP, dead cPanel, and hosting providers whose support chat is a bot. It does one thing without ceremony, and for that I forgive most of its sins. I install it on throwaway servers where I need 60 seconds of file access. I do not leave it running on production machines without IP whitelisting, a 30-character password, and a root path that jails it to a single non-critical directory.
Install it today if you manage client sites on shared hosting and you need a fast backdoor into a directory when everything else is broken. Install it if you run a home lab and want to browse files from your phone on the couch without setting up SSH keys. Install it if you can harden a PHP file and will remember to delete it afterward.
Wait if you need a permanent file management portal for multiple non-technical users. This tool will frustrate them with timeouts, tiny buttons, and a delete action that is one click too easy. Wait if you manage thousands of files per directory; the performance will disappoint you. Wait if you need an audit trail, a trash bin, or any kind of collaboration feature.
I wouldn’t text a friend about Tiny File Manager with excitement. I’d text: “Drop this PHP file in the directory, change the password, fix the thing, then delete the file before a bot finds it.” That’s the tool. That’s the whole pitch.