Most attacks on OpenCart stores are not targeted at you. A crawler finds /admin, tries a list of passwords, requests a handful of known extension paths, and moves on when nothing gives. The changes below take about ten minutes on a store you already have shell access to, and they take away the part of the attack surface automation depends on.
They are not a replacement for updates and backups. They are what you do first, because unlike updates they cost nothing to keep in place.
Minute 1: move the admin out of /admin
Renaming the admin directory has the best ratio of effort to noise removed. Choose something that is not admin, panel or yonetim, rename the folder, then fix the two lines in that folder's config.php that still point at the old name.
mv /var/www/store/admin /var/www/store/kt7-panel
# /var/www/store/kt7-panel/config.php — keep the DIR_APPLICATION line for your version
define('HTTP_SERVER', 'https://example.com/kt7-panel/');
define('DIR_APPLICATION', '/var/www/store/kt7-panel/'); // OpenCart 3.0.x
define('DIR_APPLICATION', DIR_OPENCART . 'kt7-panel/'); // OpenCart 4.xIn 4.x the same file defines DIR_OPENCART as the project root and DIR_EXTENSION next to it, so only the admin line changes. Log in through the new URL before you close the terminal, and send the team the new bookmark rather than letting them discover a 404.
Minute 3: move system/storage out of the web root
system/storage holds sessions, logs, uploads, downloadable products and — in 3.0.x — the generated modification copies of core files. Under the document root, one misconfigured rule turns all of that into public downloads. Move it above the root and repoint DIR_STORAGE in both config.php and the admin config; the cache, logs, session and upload constants are derived from it, so one line per file is enough.
mv /var/www/store/system/storage /var/www/oc-storage
chown -R www-data:www-data /var/www/oc-storage
# config.php and kt7-panel/config.php
define('DIR_STORAGE', '/var/www/oc-storage/');OpenCart 3.0.x offers to do this from the dashboard warning, and either route is fine as long as both files end up with the same absolute path. Afterwards, load the storefront and the admin, place an order in test mode, then check that nothing has quietly reappeared under the old path — a new error.log there means one config file was missed.
Minute 5: stop PHP from running where files land
If a file upload ever gets through, whether the server executes it decides between a bad afternoon and an incident. The image directory and the storage path should serve bytes and never run them.
# nginx — regex locations match in order, so put this BEFORE your general \.php$ block
location ~* ^/image/.*\.(php|phtml|phar)$ { deny all; }
location ^~ /system/ { deny all; }
location ^~ /storage/ { deny all; } # only if storage is still inside the root# Apache — image/.htaccess
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>Test it instead of assuming it. Put a file containing phpinfo() into image/, request it, and confirm you get 403 rather than a page describing your server. Then delete the file — a forgotten test file is its own vulnerability.
Minute 6: decide who can reach the login page
OpenCart limits failed customer logins — that is what the config_login_attempts setting and the oc_customer_login table are for — but that limit does not cover your staff accounts. Whatever your version does at the application layer, the cheapest and most dependable throttle for the admin sits in front of PHP, in the web server.
location ^~ /kt7-panel/ {
allow 203.0.113.10; # office
allow 198.51.100.0/24; # VPN
deny all;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
}A nested location inherits allow and deny when it defines none of its own, so the PHP handler inside stays protected. For a team that travels, swap the address list for HTTP basic auth with auth_basic and an htpasswd file, or put the panel behind a VPN. Anything that makes the login form unreachable to a crawler will do.
If an allow-list is impractical, at least make repeated failures expensive. A fail2ban jail that watches POST requests to your renamed admin path in the web-server access log and bans an address after five failures in a minute costs nothing to run, and it turns a password-spraying run into a handful of attempts.
Minute 8: users, groups and API keys
SELECT user_id, username, user_group_id, status, date_added FROM oc_user ORDER BY date_added DESC;
SELECT api_id, username, status FROM oc_api;
SELECT * FROM oc_api_ip;- One account per person and never a shared login: you cannot investigate what you cannot attribute.
- Remove the accounts of people who have left. Disabling is for an open incident; afterwards, delete them.
- Grant user_group_id 1 to as few people as the work allows, and build a narrower group for everyone else.
- Every API user in oc_api should have a matching row in oc_api_ip. If nothing uses the API, set status to 0.
- Delete the install directory if it survived setup, and confirm that directory listing is off.
Passwords deserve one sentence of their own. OpenCart accepts whatever you type, so the policy has to come from you: long, unique per person, kept in a password manager, and rotated for everyone on the day an agency or a freelancer stops working on the store.
Minute 10: stop leaking, start signalling
Two settings and four headers. In Settings, Server tab, Display Errors belongs off in production while Log Errors stays on — an error page that prints /var/www paths and a database name is a free map of your installation. Then add the response headers that cost nothing.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;While you are in the vhost, confirm that HTTPS_SERVER and HTTPS_CATALOG are set in both config files and that plain HTTP is redirected once, permanently, to the canonical host. Mixed HTTP and HTTPS definitions are the most common reason an otherwise correct admin session drops on every second click.
What ten minutes does not buy
When to write to us
If you would rather have these six done and verified on a live store — vhost changes included, with a test proving each rule blocks what it should — write to us with your OpenCart version and your hosting provider. During the launch campaign work is billed hourly at $10 per hour plus VAT, and the estimate reaches you before the work starts. First response is within two hours, Monday to Saturday, 09:00–22:00 (GMT+3).
OAuth, JWT and OWASP-driven security architecture; threat modelling.