OpenCart ships with non-friendly URLs by default, producing links such as index.php?route=product/product&product_id=42. These work everywhere but read poorly and dilute search relevance. The SEO engine rewrites them into clean paths like /mens-running-shoe, but that transformation depends on three separate layers all agreeing: the SEO toggle inside the admin dashboard, the oc_seo_url database table that maps keywords to routes, and an Apache/LiteSpeed rewrite layer that forwards pretty paths back into index.php. When any one of these drifts out of sync, you get storefront-wide 404s, duplicate content under both query-string and keyword URLs, or a catalog that silently falls back to ugly links. On LiteSpeed shared hosting the rewrite engine honors .htaccess directives exactly like Apache, so every fix below stays inside your hosting account with no root access required.

Enabling SEO URLs and laying the rewrite foundation

Start in the admin dashboard under System > Settings, edit your store, and open the Server tab. Set Use SEO URLs to Yes and save. OpenCart 4 also exposes a secondary toggle in Design > SEO URL where individual keyword mappings live. Flipping the switch alone does nothing unless the web server rewrites incoming requests, so the companion step is the .htaccess file in your document root (typically /home/USER/public_html/ for the primary domain, or the matching subfolder for an addon domain).

OpenCart distributes a template named .htaccess.txt in the install root. Using cPanel File Manager or DirectAdmin's file browser, enable Show Hidden Files (dotfiles) in the settings gear, then copy .htaccess.txt to .htaccess. If the file is missing, create it with the canonical ruleset:

Options +FollowSymlinks
Options -Indexes

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^sitemap.xml$ index.php?route=extension/opencart/module/sitemap [L]
RewriteRule ^googlebase.xml$ index.php?route=extension/opencart/feed/google_base [L]
RewriteRule ^system/storage/(.*) index.php?route=error/not_found [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]
</IfModule>

The RewriteBase / value is the part most installs get wrong. If OpenCart lives in a subdirectory such as /home/USER/public_html/shop/, set RewriteBase /shop/ instead. A mismatched base is the single most common reason every product and category link returns a 404 while the homepage loads fine, because the homepage never needs the rewrite but every deeper route does. After editing, load any SEO link in a private browser window. If you see the OpenCart 404 page, the rewrite engine is reaching index.php but the keyword is unknown; if you see the LiteSpeed or server 404 page instead, the rewrite rule itself is not firing and the .htaccess path, base, or syntax needs a second look. Check error_log in the same directory through File Manager for lines mentioning rewrite or 500 errors, which usually point at a stray directive left over from a prior host.

Keyword mappings, missing slugs, and the seo_url table

Even with rewrites firing cleanly, individual pages 404 when no keyword exists for that route. OpenCart stores every mapping in the oc_seo_url table (your prefix may differ). Each row ties a key and value, for example product_id = 42, to a human keyword and a store_id and language_id. A product added through a quick import or an older extension frequently lacks this row, so the link falls back to the query string or breaks entirely.

Assign keywords in the dashboard by editing a product under Catalog > Products, opening the SEO tab, and entering a unique keyword per language and store. Keywords must be globally unique across the table; two products sharing shoes will collide and one will refuse to resolve. To audit gaps in bulk, open phpMyAdmin from cPanel or DirectAdmin, select your database, and run a read-only query:

SELECT p.product_id
FROM oc_product p
LEFT JOIN oc_seo_url s
  ON s.`key` = 'product_id'
  AND s.`value` = p.product_id
WHERE s.seo_id IS NULL;

Any rows returned are products with no SEO keyword. Rather than inserting rows by hand, which risks duplicate keywords and missing store_id or language_id values, feed those IDs back into the admin and fill the SEO tab so OpenCart writes clean rows. If you need to clear a corrupted mapping, delete only the specific offending row in phpMyAdmin after exporting a backup of the table first. Avoid any server-wide SQL; everything here is scoped to your own database user. Duplicate keyword errors in the admin almost always trace back to leftover rows from a deleted product, so searching the table for the conflicting keyword and removing the orphan resolves the save failure.

Canonical tags, HTTPS redirects, and killing duplicate content

Friendly URLs introduce a classic duplication problem: the same product is reachable at /mens-running-shoe, at index.php?route=product/product&product_id=42, with and without a trailing slash, and across http, https, www, and bare-domain variants. OpenCart emits a <link rel="canonical"> tag on product, category, and manufacturer pages pointing at the preferred SEO URL, which handles most search-engine consolidation automatically. Confirm your store URL is set correctly and uses HTTPS in System > Settings > Server (SSL URL) and Store tab, because a mismatch there makes canonicals point at the wrong scheme.

Enforce a single hostname and HTTPS at the .htaccess layer so visitors and crawlers land on one canonical origin. Place these lines immediately after RewriteEngine On and before the OpenCart catch-all rule:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]

This collapses both the www and non-HTTPS cases into one 301 redirect to the bare HTTPS domain. Swap the target to https://www.%1 if you prefer the www form; pick one and keep it consistent with the store URL in the dashboard. Order matters: these redirect rules must run ahead of the _route_ rewrite, otherwise a request can be rewritten into index.php before the canonical redirect ever fires. Use a single R=301 permanent redirect rather than chaining multiple hops, since redirect chains waste crawl budget and slow the first byte.

For retired products or renamed category slugs, add explicit one-to-one redirects near the top of the file so old inbound links keep their link equity: Redirect 301 /old-shoe-url https://example.com/mens-running-shoe. If you run an OpenCart install alongside another platform and need to compare how callback and redirect handling differs under LiteSpeed, the patterns in our PrestaShop payments and webhooks guide cover similar rewrite and TLS considerations.

Sitemap generation, caching, and final verification

OpenCart's SEO sitemap is served dynamically through the module route referenced in the first .htaccess rule. Enable it under Extensions > Extensions > Modules > Google Sitemap (or SEO & Metadata depending on your 4.x build), set status to Enabled, and save. Visit https://example.com/index.php?route=extension/opencart/feed/google_base or the mapped /sitemap.xml directly to confirm valid XML renders rather than a 404 or blank page. If the pretty /sitemap.xml path fails while the full query-string route works, the sitemap rewrite line is missing or sits below the catch-all rule where it never executes.

Submit the resolved HTTPS sitemap URL to Google Search Console so crawling reflects your canonical hostname. After any URL or rewrite change, clear the OpenCart caches from the Dashboard cache buttons (top-right in the 4.x admin) and from System > Maintenance, because stale theme or SEO caches can keep serving old links. If your PHP version was recently switched in the cPanel MultiPHP Manager or DirectAdmin Select PHP Version tool, confirm a modern 8.x branch is active, since OpenCart's SEO routing and sitemap generation rely on current PHP string and URL functions. End by spot-checking a product, a category, a manufacturer page, and the homepage in a fresh incognito session, watching for clean 200 responses and correct canonical tags in the page source. Any remaining 404 or 500 will be recorded in the directory-level error_log, which you can open in File Manager to read the exact failing route without ever needing server administrator access.