The local WordPress environment was running. The database container was healthy. My credentials came from a working production site.
Then the export failed:
ERROR 1044: Access denied for user ...
The database appeared not to exist under the name I had copied from the WordPress configuration. The credentials were not wrong. I was looking at the wrong layer of the hosting system.
The missing prefix
ISPConfig is designed to manage multiple clients on one server. It prefixes resources so two clients can choose the same friendly database name without colliding.
Conceptually, the server looked like this:
Client 1
c1socalnomaddb
c1socalnomadusr
Client 2
c2clientdb
c2clientusr
The WordPress configuration and ISPConfig interface did not initially make the distinction obvious to me. I was thinking in terms of the friendly name. MariaDB was enforcing the actual prefixed account and database names.
The useful lesson was simple: when a control panel manages database identities, do not assume the name displayed in one application is the exact server-level name required by every administrative tool.
Finding the authoritative name
There were several ways to verify it.
The safest was ISPConfig itself: open Sites → Databases and inspect the full database and user names.
From the server, an administrator could also list matching databases:
SHOW DATABASES LIKE '%socalnomad%';
The result exposed the c1 prefix.
The ISPConfig filesystem follows a related pattern. A site’s human-readable path may be a symlink:
/var/www/example.com
-> /var/www/clients/client1/web11
The friendly domain path is easier for humans; the client1/web11 path reflects ISPConfig’s internal ownership model. Database naming follows the same general idea.
Exporting without putting passwords in scripts
My early import script prompted for the production password and interpolated it into a remote shell command. It worked, but embedding passwords in command lines is undesirable: quoting is fragile, and process lists or shell history can expose more than expected.
A better pattern is to use credentials already secured on the server, a restricted defaults file, or WP-CLI under the site’s operating-system account:
ssh user@example-server \
'wp --path=/var/www/example.com/web db export -' \
> production.sql
That keeps database credential handling inside WordPress and the server configuration.
The local import remains straightforward:
docker exec -i my-wp-db \
mariadb -u wpuser -plocal_only_password wordpress_local \
< production.sql
The production name and the local name do not need to match. The dump contains schema and data; the import target is whichever local database WordPress is configured to use.
Rewriting the site URL
After import, WordPress still believed it lived at the production domain.
Updating only home and siteurl may make the dashboard accessible, but URLs can also appear inside post content, widget settings, and serialized options. WP-CLI is the safer general tool:
docker exec my-wp \
wp search-replace \
'https://example.com' \
'http://localhost:8080' \
--all-tables \
--skip-columns=guid
The guid column is normally left unchanged because it is an identifier, not a display URL.
Why the import still was not finished
Once the database loaded, production plugins and settings arrived with it:
- Security plugins expected production IP addresses and authentication flows.
- Caching plugins could hide local changes.
- Email and analytics integrations could accidentally contact external services.
- Media references still pointed to files that were not part of the database.
A database import is not the same thing as a complete local clone. It is a starting point that needs an environment-specific cleanup pass.
For my setup, that meant:
- Confirm the full ISPConfig database identity.
- Export through the server’s trusted configuration.
- Import into the local MariaDB container.
- Rewrite production URLs.
- Disable or reconfigure production-only integrations.
- Verify the theme against representative content.
Follow The Name Back To Its Owner
The c1 prefix was not an arbitrary obstacle. It was evidence that I was crossing boundaries between three different naming systems:
- WordPress application configuration
- ISPConfig’s client-facing resource model
- MariaDB’s actual server accounts and databases
The import failed because I treated those layers as if they were one.
Once I stopped asking, “Why is this password wrong?” and started asking, “Which system owns this name?”, the problem became much easier to solve.