HomeExtensionsScriptsWordPressDocsSupport
PHP scriptv1.1.1

ImageHost

Free PHP script to run your own image hosting website. Visitors upload images and get share links and embed codes. Members get albums, an API key and two-factor sign in. You run everything from a separate admin panel.

This guide covers installing and running ImageHost (version 1.1.1), a free PHP script for your own image hosting website. Use it for your first install, as a reference for every admin screen, and to connect your own code through the upload API.

Free and open source

ImageHost is released under the GNU GPL version 3, free for personal and commercial sites. The license adds one term under section 7(b): the Developed by Quintaphon Services credit in the public footer must stay visible. The full text is in LICENSE.txt.

Overview

A visitor uploads an image and gets a share page such as https://img.example.com/i/XXt9uKT, a direct link and ready-made embed codes. Members keep their images in albums and can upload from their own tools with an API key. The admin area runs on its own address with its own sign in.

  • Public pages: upload, image page, Explore gallery, random image, members, member galleries, tools, contact, report, DMCA, and the About, Terms and Privacy pages.
  • Members: My images with albums and bulk actions, account settings with two-factor sign in, API key and ShareX setup.
  • Admin (default /admin/login): dashboard, images, bulk tools, members, DMCA, inbox, bans, settings, pages, SEO, permalinks, storage, security, database, updates, maintenance and logs.
  • No dependencies: plain PHP with PDO, so it runs on shared hosting with SQLite or on a VPS with MySQL.

What ships in the package

  • index.php: the front controller that handles every page.
  • app/: the code (routes, templates, database layer). Blocked from the web by .htaccess.
  • assets/: CSS, JavaScript and the Inter font.
  • storage/: the SQLite database, cache, logs, sessions and backups. Blocked from the web.
  • uploads/: the images, in YYYY/MM folders. Scripts cannot run here.
  • .htaccess, robots.txt, main-sitemap.xsl (sitemap styling), router.php (for PHP's built-in server), README.md and LICENSE.txt.

The installer writes config.php in the site folder. It holds the database details and a secret key, so it is never in the ZIP and you should never share it.

Requirements

ItemNeeded
PHP8.1 or newer (tested on 8.1 to 8.5)
PHP extensionspdo_sqlite or pdo_mysql, gd, mbstring
Recommendedcurl (URL uploads, captcha, R2), exif (turns photos upright), GD with WebP, zip (updates and ZIP imports)
DatabaseSQLite (nothing to set up) or MySQL / MariaDB
Web serverApache with mod_rewrite, LiteSpeed / OpenLiteSpeed, or Nginx (see Nginx)
HTTPSStrongly recommended for any public site

Without URL rewriting every page still works as index.php?r=..., so you can test on any server first.

Install

  1. Download imagehost.zip and unzip it.
  2. Upload the contents of the imagehost folder to your web root, or to a sub folder such as /images/. The .htaccess file finds the sub folder by itself.
  3. Make storage/ (with its sub folders) and uploads/ writable by PHP, and the site folder writable once so the installer can create config.php.
  4. Open your site in a browser. The installer checks PHP and the folders, then asks for the database: pick SQLite for zero setup, or MySQL and enter the host, database name, user and password.
  5. Enter the site name, site address and your main admin account. The installer creates the tables and signs you in.

Reinstall

Delete config.php (and the old database tables or the SQLite file in storage/), then open the site again.

After setup

The admin dashboard lists anything that still needs attention. Work through these first:

  1. Settings > General: check the site address (every link and email is built from it), name, tagline and accent colour.
  2. Settings > Mail and forms: pick PHP mail or SMTP and send a test email. Password resets, sign-in codes and DMCA replies need working mail.
  3. Security: move the admin to a private address, turn on two-factor sign in, and add Cloudflare Turnstile, reCAPTCHA or hCaptcha keys.
  4. Settings > Uploads: set file size limits, uploads per hour and per day for guests and members, and allowed types.
  5. Raise PHP upload_max_filesize and post_max_size if you want bigger uploads than the server allows. The dashboard warns when they are lower than your limits.
ImageHost admin dashboard screen
The dashboard shows totals, a 30-day chart and setup warnings.

Public site

The homepage is the uploader. Visitors drop images, browse for them, paste a screenshot with Ctrl+V, add a list of URLs, or scan a web page and tick the images to copy. Before uploading they choose public or private and an optional resize preset.

ImageHost upload page screen
The upload page.

When the uploads finish, each image shows its share link, a copy button and, for guests, a private delete link. Tabs switch between share links, direct links, BBCode, HTML and Markdown, and Copy all copies every link at once.

ImageHost upload results screen
Links appear the moment each image lands.

The image page shows the picture with zoom (only when it is bigger than the frame), size, type, owner and album, star ratings, and a share box with every link format. Arrow keys move to the previous and next image.

ImageHost image page screen
The image page.

Explore lists public images by newest, top rated or most viewed. Private images, images in private albums and images of private profiles never appear there, in the random image or in sitemaps.

ImageHost explore screen
Explore, the public gallery.

Every public page follows the visitor's light or dark preference, and the sun and moon button switches it.

ImageHost dark mode screen
An image page in dark mode.

Members

Visitors can create an account (you can turn registration off). Members sign in with their username or email and get:

  • My images: search, sort, albums (public or private), and bulk move, make private or public, copy links or delete.
  • A public profile at /u/name with their public images. Members can make their profile private, which also removes them from the Members page.
  • Account settings: email, password (with a strong password generator), two-factor sign in, emailed sign-in codes, new-device alerts, the last 7 sign-ins, sign out of other devices, the API key and ShareX setup.
  • Delete account: needs the password, the two-factor code when it is on, typing DELETE, and then a confirmation link sent by email. Members choose whether their images are deleted or stay online as guest uploads.
ImageHost my images screen
My images with albums.
ImageHost account security screen
Account security and the API key.

Admin panel

The admin area is separate from the member side: it has its own sign in at /admin/login (or your private address), its own session and its own layout. Signing in as a member never gives admin rights. Signing in as an admin also signs you in on the site, so you do not have to sign in twice.

ImageHost admin sign in screen
The admin sign in.
PageWhat it does
DashboardTotals, 30-day chart, latest uploads, new members, unread messages and setup warnings.
ImagesSearch by code, title, owner, IP or storage. Bulk delete, delete and block re-upload, change privacy, ban the uploader's IP, move to R2, export CSV.
Bulk toolsUpload any number of images, import a ZIP or a server folder (storage/import), and delete by filters (age, owner, IP, views, size, type, visibility) with a preview first.
MembersAdd, edit, change role, reset a password or 2FA, ban (with IP), delete with or without images, bulk actions, CSV export.
DMCATakedown notices with status and notes. Remove the images, block the exact files from coming back, and reply to the claimant by email.
Inbox and BansContact messages and abuse reports (with one-click delete of reported images), and IP or member bans.
Settings, Pages, SEO, PermalinksSee Settings and SEO and permalinks.
Storage, SecuritySee Cloudflare R2 and Security.
DatabaseOptimize, check, repair and analyze tables, see table sizes, and download a backup (SQLite file or SQL dump).
UpdatesSee Update.
MaintenanceRebuild thumbnails, run the scheduled jobs, remove missing files, delete stray files, recount totals and clear caches.
LogsActivity log (admin sign ins and changes), error logs, robot logs, mass email and system info.
ImageHost images screen
Images, with filters and bulk actions.

Only the main administrator (the account made at install) can change mail server details, the site address, security settings, storage, pages and roles, read the error logs, or install updates. Other administrators can moderate content and members.

Settings

TabSettings
GeneralSite name, tagline, site address, accent colour, time zone, date format, images per page, footer text, URL style, favicon, social links (up to 6, reorderable), announcement bar, maintenance mode.
UploadsFile size for guests and members, files per upload, maximum dimensions, uploads per hour and per day, delete old guest uploads, URL uploads and web page scan.
ImagesAllowed types, thumbnail size, format and fit, info bar, preview size, strip EXIF, private by default, serve images through PHP (with bandwidth caps).
AccessGuest uploads, registration, public gallery, member list, ratings, random image, guest delete links, the API.
Mail and formsMail transport (PHP mail, SMTP or a log file for testing), SMTP server, sender address, test email, contact and DMCA forms.
Ads & filesAd code above and below the image on image pages (optionally hidden for members) and the ads.txt editor.
AdvancedMinified HTML for public pages, how long logs are kept (72 hours by default), robot logging and other fine-tuning.
ImageHost settings screen
Settings, General tab.

Pages edits the About, Terms of service and Privacy policy pages with a visual editor or as HTML.

Security

  • Private admin address: change /admin to something only you know. The old address returns 404.
  • Two-factor sign in: scan the QR code with Google Authenticator, Authy, 1Password or Bitwarden and keep the 8 recovery codes. You can require 2FA for every administrator.
  • Emailed sign-in codes for admins and members. They can only be switched on after a test email went out.
  • Sessions: idle timeout, sessions tied to the IP address, an admin IP allow list, and "sign out all admins".
  • Captcha: Cloudflare Turnstile, Google reCAPTCHA v2, hCaptcha or a built-in math check, switchable per form (member sign in, register, forgot password, admin sign in, contact, report, DMCA).
  • Lockout after failed sign ins (per IP and per account), minimum password length, force HTTPS, security headers, and the visitor IP source (direct, Cloudflare or a proxy).
ImageHost security screen
Security settings.

Locked out?

Add 'admin_ip_allow_off' => true, (ignores the IP allow list) or 'disable_2fa' => true, (skips the admin two-factor code) to the array in config.php, sign in, fix the setting, then remove the line again.

Behind the scenes: prepared statements everywhere, escaped output, CSRF tokens on every form and API call, hashed passwords, rate limits that hold under parallel requests, authenticator codes that cannot be reused, uploads checked by content with a pixel limit, and URL uploads that cannot reach private or internal addresses.

SEO and permalinks

SEO sets the homepage title, the title format for other pages ({title} and {site}), descriptions, keywords, a default share image, your X account, Google and Bing verification, a no-index switch and the robots.txt file.

sitemap.xml is an index of pages.xml, images-1.xml, images-2.xml and so on, and members-1.xml, with up to 1,000 entries each. Private images, images in private albums and private profiles are never listed. The sitemaps are styled by main-sitemap.xsl so they are readable in a browser.

ImageHost seo screen
SEO settings.

Permalinks renames the public addresses (image page, profile, gallery, members, download, tools, pages, contact, report, DMCA) and the image code length. Old addresses redirect with a 301.

Cloudflare R2

  1. In the Cloudflare dashboard, open R2 and create a bucket.
  2. Turn on public access (an r2.dev address) or connect a custom domain such as cdn.example.com.
  3. Create an R2 API token with Object Read and Write for that bucket.
  4. In Admin > Storage, enter the account ID, access key ID, secret access key, bucket name and the public bucket address (it must start with https://), then press Test the connection.
  5. Turn on Offload new uploads to R2. Choose whether to keep local copies too.
  6. Use Move existing images to R2 to copy older images to the bucket in batches.
ImageHost storage (r2) screen
Storage settings for Cloudflare R2.

Image links then point to the bucket address. Deleting an image also deletes it from the bucket.

Scheduled jobs

Every hour, ImageHost deletes guest uploads older than your limit (when set), clears logs older than 72 hours (Settings > Advanced), and removes expired rate limits, links and temp files. By default this runs by itself after a page view. For exact timing, call the secret cron address shown under Maintenance from a real cron job:

*/15 * * * * curl -s https://img.example.com/cron/your-secret-token > /dev/null

Update

Admin > Updates shows your version and a Check for a newer version button that opens this script's page. To update:

  1. Download the new imagehost.zip. Do not unzip it.
  2. Upload it under Admin > Updates and press Back up and update.
  3. The current code is saved to storage/backups first. config.php, storage/, uploads/, robots.txt and ads.txt are never touched, and database changes run on the next page load.
  4. If something looks wrong, press Restore next to the backup.
ImageHost updates screen
Updates: check, install and restore.

Without the PHP zip extension, copy the new files over the old ones by FTP instead (keep config.php, storage/ and uploads/).

Nginx

Apache and LiteSpeed use the shipped .htaccess. On Nginx, add rules like these to the server block (prefix the paths with the folder name for a sub folder install):

location ~ ^/(app|storage)/ { deny all; }
location = /config.php { deny all; }
location ~ ^/uploads/.*\.(php|phtml|phar|html?|svg|js)$ { deny all; }
location / {
    try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param IH_REWRITE 1;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

Upload API

Any program that can send an HTTP request can upload to your ImageHost site: your own PHP site, a Python script, a Node.js app, a desktop tool or ShareX. Uploads made with a key belong to that member and appear under their My images.

1. Get an API key

  1. As the admin, make sure Settings > Access > API is on.
  2. Sign in as a member and open Account settings. The API and ShareX card shows your key. New key replaces it and stops the old one.

Keep the key secret

Anyone with the key can upload as you. Use it from server-side code, scripts and desktop tools, never in JavaScript that runs in a visitor's browser. For uploads from other websites, use the embed uploader instead.

2. Send the request

POST https://img.example.com/api/upload as multipart/form-data. If your server has no URL rewriting, use https://img.example.com/index.php?r=api/upload.

FieldRequiredDescription
X-API-Key header (or key field)YesYour API key.
fileOne of file or urlThe image file (JPG, PNG, GIF, WebP and any other type you allow). One image per request.
urlOne of file or urlAn image address to copy instead of sending a file (needs URL uploads on).
titleNoImage title. Defaults to the file name.
privateNo1 for private (only the owner and admins can see it), 0 for public. Defaults to the site setting.
resizeNoResize preset: 100, 150, 320, 640, 800, 1024, 1280, 1600, 1920 or 2560 (the longest side fits that box). Leave empty for the original size.
album_idNoThe ID of one of your albums (shown in the address when you open the album under My images).

3. Read the response

Every response is JSON. On success ok is true and image holds the links. Use image.urls.direct for an <img> tag and image.urls.viewer for the share page; image.codes has ready-made HTML, BBCode and Markdown.

{
  "ok": true,
  "image": {
    "code": "XXt9uKT",
    "title": "Harbour at dusk",
    "width": 2400,
    "height": 1350,
    "size": 195612,
    "size_h": "191.0 KB",
    "ext": "jpg",
    "private": false,
    "urls": {
      "viewer": "https://img.example.com/i/XXt9uKT",
      "direct": "https://img.example.com/uploads/2026/10/XXt9uKT.jpg",
      "thumb": "https://img.example.com/uploads/2026/10/XXt9uKT_t.webp",
      "download": "https://img.example.com/d/XXt9uKT"
    },
    "codes": {
      "viewer": { "label": "Share link", "value": "https://img.example.com/i/XXt9uKT" },
      "direct": { "label": "Direct link", "value": "https://img.example.com/uploads/2026/10/XXt9uKT.jpg" },
      "html_thumb": { "label": "HTML thumbnail", "value": "<a href=\"https://img.example.com/i/XXt9uKT\" ...>" },
      "bb_thumb": { "label": "BBCode thumbnail", "value": "[url=https://img.example.com/i/XXt9uKT][img]...[/img][/url]" },
      "html_full": { "label": "HTML full image", "value": "..." },
      "bb_full": { "label": "BBCode full image", "value": "..." },
      "markdown": { "label": "Markdown", "value": "[![Harbour at dusk](...)](https://img.example.com/i/XXt9uKT)" },
      "markdown_full": { "label": "Markdown full image", "value": "![Harbour at dusk](...)" },
      "link_us": { "label": "Link to us", "value": "..." }
    },
    "delete_url": "https://img.example.com/delete/XXt9uKT?key=c622a28fbea25541da0d3475"
  }
}

On failure ok is false and error says why in plain words:

HTTP statusMeaning
400No file was sent, or the upload failed on the server.
401Invalid API key.
403The API or URL uploads are turned off, uploads are closed, or the account is banned.
405The request was not a POST.
413The request is bigger than the server's post_max_size.
422The image was refused: wrong type, too large, too many pixels, not a real image, removed before (DMCA), or the daily limit is reached.
429Too many uploads this hour. Wait and try again.

cURL (command line)

curl -H "X-API-Key: YOUR_API_KEY" \
     -F "[email protected]" \
     -F "title=Holiday" \
     https://img.example.com/api/upload

Copy from an address instead of sending a file:

curl -H "X-API-Key: YOUR_API_KEY" \
     -F "url=https://example.org/photo.png" \
     https://img.example.com/api/upload

PHP

For example, a form on your own website that sends the visitor's image to ImageHost on the server side, so the key never reaches the browser:

<?php
// upload-to-imagehost.php
const IMAGEHOST_URL = 'https://img.example.com/api/upload';
const IMAGEHOST_KEY = 'YOUR_API_KEY';

/** Uploads a local file and returns the decoded JSON response. */
function imagehost_upload(string $path, string $title = '', bool $private = false): array
{
    $ch = curl_init(IMAGEHOST_URL);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 120,
        CURLOPT_HTTPHEADER => ['X-API-Key: ' . IMAGEHOST_KEY, 'Accept: application/json'],
        CURLOPT_POSTFIELDS => [
            'file' => new CURLFile($path),
            'title' => $title,
            'private' => $private ? '1' : '0',
        ],
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        return ['ok' => false, 'error' => curl_error($ch)];
    }
    return json_decode($body, true) ?? ['ok' => false, 'error' => 'Not a JSON response'];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
    $res = imagehost_upload($_FILES['photo']['tmp_name'], $_FILES['photo']['name']);
    if ($res['ok']) {
        $img = $res['image'];
        echo '<a href="' . htmlspecialchars($img['urls']['viewer']) . '">'
           . '<img src="' . htmlspecialchars($img['urls']['thumb']) . '" alt=""></a>';
    } else {
        echo 'Upload failed: ' . htmlspecialchars($res['error']);
    }
}

Python

import requests

API_URL = "https://img.example.com/api/upload"
API_KEY = "YOUR_API_KEY"

with open("holiday.jpg", "rb") as f:
    r = requests.post(
        API_URL,
        headers={"X-API-Key": API_KEY},
        files={"file": f},
        data={"title": "Holiday", "resize": "1920"},
        timeout=120,
    )

data = r.json()
if data["ok"]:
    print("Share page:", data["image"]["urls"]["viewer"])
    print("Direct link:", data["image"]["urls"]["direct"])
else:
    print("Error", r.status_code, data["error"])

JavaScript (Node.js 18 or newer)

import { readFile } from "node:fs/promises";

const form = new FormData();
form.append("file", new Blob([await readFile("holiday.jpg")]), "holiday.jpg");
form.append("title", "Holiday");

const res = await fetch("https://img.example.com/api/upload", {
  method: "POST",
  headers: { "X-API-Key": process.env.IMAGEHOST_KEY },
  body: form,
});
const data = await res.json();
console.log(data.ok ? data.image.urls.direct : `Error ${res.status}: ${data.error}`);

ShareX

On Windows, ShareX can send every screenshot straight to your site. Under Account settings > API and ShareX, press ShareX config and open the downloaded .sxcu file. ShareX adds your site as a custom uploader with the key, the direct link as the result, and the thumbnail and delete links filled in.

Embed uploader (no key needed)

To let visitors of another website upload to your site, paste the embed code from the Tools page. It shows the uploader in an iframe and uses a signed guest token, so no key is exposed:

<iframe src="https://img.example.com/embed" width="100%" height="420"
        style="border:0;border-radius:8px;max-width:720px"
        title="ImageHost uploader" loading="lazy"></iframe>
ImageHost tools screen
Tools: embed code, upload button and ShareX setup.

Limits and tips

  • Send one image per request. To upload many, loop over them; three requests at a time is a good pace.
  • Uploads per hour and per day follow Settings > Uploads (members get three times the hourly guest limit). On 429, wait before retrying.
  • Size, type and pixel limits are the same as on the website. Check ok and show error to your users.
  • Store image.code if you need to find the image again, and keep delete_url private: anyone who opens it can delete the image.

Troubleshooting

ProblemFix
Installer says a folder is not writableGive the PHP user write access to storage/ (and sub folders), uploads/, and the site folder for config.php.
Pages other than the homepage show 404URL rewriting is off. Enable mod_rewrite and AllowOverride All, add the Nginx rules, or set URL style to plain in Settings.
Big images fail to uploadRaise PHP upload_max_filesize, post_max_size and memory_limit. The dashboard shows the current server limit.
"Too many megapixels"The image is bigger than the pixel limit (40 MP for guests, 100 MP for members). Resize it first.
Password reset or sign-in emails never arriveCheck the site address in Settings, then send a test email under Mail and forms. Use SMTP if PHP mail is blocked by your host.
Locked out of the adminSee Locked out? under Security.
Visitor IPs all look the same behind CloudflareSet the visitor IP source to Cloudflare under Security.
API answers 401The key was replaced or mistyped. Copy it again from Account settings.
R2 test failsCheck the account ID, that the token has Object Read and Write for this bucket, and that the public address starts with https://.
"This is not an ImageHost package"Upload the original imagehost.zip without unzipping or re-zipping it.

Changelog

1.1.1 (October 23, 2026)

  • Far fewer session files: guests and search engine crawlers no longer get one on every visit. A guest's form token now lives in a cookie, and a session starts only when someone signs in or a message has to be shown.
  • The math captcha no longer needs a session, and guest view counts use a small cookie.
  • Scheduled jobs remove idle guest sessions after 2 hours and any session after 24 hours.

1.1.0 (October 23, 2026)

First public release, free and open source under the GNU GPL version 3.

  • Built for PHP 8.1 to 8.5, with SQLite or MySQL and no dependencies.
  • Separate admin area with its own sign in, private address, idle timeout, IP lock, allow list and two-factor sign in.
  • Member security: two-factor sign in with recovery codes, emailed sign-in codes, new-device alerts, recent sign-ins, account deletion confirmed by email.
  • Captcha per form (Turnstile, reCAPTCHA, hCaptcha or math), DMCA notices, bulk tools, CSV exports.
  • SEO, permalinks, split sitemaps, Cloudflare R2 offload, database tools, and ZIP updates with backup and restore.
  • Upload API with personal keys, ShareX config and an embeddable uploader.

Need help?

Read this guide from install through the admin screens first. If something still fails, note the exact address, the message on screen and your PHP version, then contact us on the support page.