Testing a change on the live site is how sites break in public. A copy within the same hosting account takes fifteen minutes and removes that entirely.
1. A subdomain with its own directory
In cPanel, Subdomains (or the Domains interface on newer versions). Create dev.example.com.
Watch the document root it proposes. By default it lands inside public_html, which means the copy is also reachable through the main site's address as a folder, and indexed as such.
Put it outside instead, at something like /home/username/devCreating and managing subdomains walks through why the document root is the setting that matters.
2. Copy the files
In File Manager, compress the site directory, move the archive, extract it. That is dramatically faster than copying thousands of files individually and avoids the transfers that silently drop one or two.
Exclude the caching directories and any backup archives while you are there. They are usually the bulk of the size and none of the value.
3. Copy the database
Export the live database from phpMyAdmin, create a new database and user in cPanel, and import the export into it.
Give the new one an obviously different name, user_sitedev rather than user_site2. You will be looking at both in a list later, under pressure, and the distinction should be unmistakable.
Managing MySQL databases with phpMyAdmin goes over the export and import.
4. Point the copy at the new database
This is the step that gets missed, and missing it is worse than not having a copy at all.
A copy still configured for the live database reads and writes live data. Every change you make while "testing" is a change to the real site, with no indication that anything is wrong: the pages just look right.
Edit the configuration file, wp-config.php for WordPress, and change the database name, user and password to the new ones. Then confirm by changing something trivial on the copy and checking the live site did not change.
For WordPress the site address also has to be updated, or the copy redirects visitors to the live site. There is more on doing it without breaking serialised data in changing your WordPress site URL.
Keep it off the internet
Two problems, one solution.
The copy can be indexed, competing with the real site and occasionally receiving visitors who order from it. And it holds real customer data and can send genuine-looking mail if anything triggers a notification.
Password protecting the subdomain handles both by keeping everyone out. Password protecting a directory goes into it, and it is simpler than configuring each protection separately.
For a store, disable outgoing mail and switch payment gateways to test mode as well, because those can reach the outside world regardless of who can see the pages. Running a staging store deals with the store-specific rules, including why its database must never be pushed back.
It shares the account
The copy uses the same disk allowance, the same processing allowance and the same inode count as the live site.
A copy of a large site can double the disk usage, and a forgotten copy is a common reason an account fills up. Understanding inodes explains the limit that is not bytes.
Take a copy each time
A permanent development site drifts from the live one, and testing against a three-month-old copy proves very little.
Make a fresh copy for each piece of work and remove it afterwards. It is fifteen minutes, and it is the difference between a test and a guess.
Moving changes back
Files can be copied to the live site. The database should not be, if the live site has changed, orders, comments, content edits since the copy would all be overwritten.
Repeat configuration changes by hand on the live site instead, from a list you kept while working. Tedious, and it is the only version that does not lose data.
Copy the files without copying the rubbish
A site directory contains a great deal that a copy does not need, and excluding it makes the operation faster and the copy smaller.
rsync -a --exclude='wp-content/cache/' --exclude='wp-content/uploads/' \ --exclude='*.zip' --exclude='*.sql' ~/public_html/ ~/dev/
Excluding uploads is the significant one: on most sites it is the bulk of the size and it is rarely what you are testing. Where the work needs images, link to the live directory rather than copying it. The copy then displays real content without duplicating gigabytes.
Note that a symbolic link means the copy can write to the live uploads. Use it only where the work is read-only, and copy properly otherwise.
Confirm the copy is genuinely separate
The check that catches the mistake this whole exercise exists to prevent:
grep DB_NAME ~/dev/wp-config.php ~/public_html/wp-config.php
Two different values, plainly visible. If they match, the copy is writing to the live database and every change made while testing is a change to the real site.
Then verify behaviourally rather than by reading configuration: change a page title on the copy and confirm the live site did not change. That takes thirty seconds and it is the only test that proves the separation instead of assuming it.
Copying a store or a membership site
Sites with user accounts and orders need three additional steps before the copy is touched.
Payment gateways to test mode, outgoing mail disabled, and any integration credentials replaced with test values, because a copy with live API keys can create real records in another system while you believe you are testing.
That last one catches people: the site is on a private subdomain and its integrations are still talking to production. Running a staging store sets out the store-specific rules in full.
What the copy is not suitable for
Worth stating, because a copy that passes a test is easily mistaken for proof.
It shares the account's resources, so it cannot test performance under load, and testing that on the same account degrades the live site.
It has different data volumes, so a query that is fast on a copy with a hundred orders may be slow on the live site with fifty thousand.
And it is a different hostname, so anything hard-coded to the live address behaves differently, which is useful to discover and means "it worked on staging" is a weaker statement than it sounds. For measuring the real one, see telling whether a slow site is the server or the site.