The important feature of a password salt (as Terry Chia correctly notes) is that it should be globally unique: no two password hashes (even on different sites) should ever have the same salt.
Using unique salts protects against various related attack methods that might be employed by an attacker who has compromised the user database and wants to recover the passwords from their hashes:
First of all, it prevents an attacker from easily telling if several users have the same password (which would make those users obvious target, since such a common password would likely be easy to guess and provide a high payoff).
Conversely, it also prevents an attacker who has compromised multiple sites from being able to easily tell which users have used the same password on both sites.
Further, using a unique salt for each user prevents an attacker from obtaining any benefit from attacking multiple accounts at the same time. Without salts (or with non-unique salts), an attacker could hash a guessed password and then compare it against every password hash in the database, making it more likely that they'll obtain at least one match. With unique salts, this is impossible, since the attacker has to choose the salt (and thus a single target user) before hashing each password guess.
Finally, unique salts also make it impractical to use precomputed hash look-up tables (like the so-called "rainbow tables") to speed up password cracking, since, essentially, a separate table would be needed for every user.
Basically, the main goal of salting is to ensure that an cracking multiple compromised password hashes together should be no easier than cracking each one of them separately — nothing more and nothing less. Using globally unique salts achieves this goal.
One easy way to ensure global uniqueness (with high likelihood) is to use a sufficiently long random string as the salt. However, other ways exist too.
For example, you could obtain a globally unique salt by combining a locally unique user identifier (such as an e-mail address or an account number) with a fixed, globally unique site identifier (e.g. the domain name of a website), and possibly some other items like a purpose designator (in case you might need more than one unique salt per user) and a timestamp (so than a new password will always get a different salt). For example, the following string would be perfectly good globally unique salt:
"This is a password salt for user 5457 on security.stackexchange.com created at 1 September 2013 13:35:23.94730 UTC"
and so would this one too:
"password_salt:5457:security.stackexchange.com:20130901T133523.94730"
If you'd prefer your salts to be more compact, you could also feed either of the strings above through a cryptographic hash function like SHA-256 and use the output as the salt. Or, if your system provides a built-in GUID generator, you could just use that (assuming that it works as it should, of course).
All that said, there is one potential benefit to making password salts not only unique but also unpredictable: it prevents the attacker from starting a brute-force attack before they've compromised your database.
Basically, if I, as an attacker, knew that your admin account's salt was, say, "12345678", then I could set my computer(s) to compile a lookup table of more or less common passwords hashed with that salt even before I'd found a way to obtain your actual password hash. Once I did find a way to do that (maybe weeks or months later), I could look up the hash in my table, hoping that it might match one of the passwords my program had already tried in the mean time, in which case I would know the password immediately.
However, if I didn't even know what the actual salt you used was, I wouldn't be able to start guessing the password before I first obtained the correct salt. That would take extra time, potentially allowing you to change the password before I managed to crack it.
Again, long random salts are (with high probability) not only unique but also unpredictable. That said, there are other ways to achieve this goal, too. For example, you could take either of the unique example salts shown above and just append some secret value (e.g. a random string stored in a config file, possibly changed regularly) to it. Since this secret would be the same for all users, it wouldn't help with uniqueness, but it would ensure that I wouldn't be able to start a brute-force attack without first somehow learning the secret.
Addendum: Regarding your edits to the question, I'd say that your scheme (using domain name + user ID + pepper as the salt) should be enough. If possible, I'd also include a timestamp, but that obviously requires you to store the timestamp somewhere.
That said, I still find your premise ("the salt is not stored in the database") a bit puzzling. If you can store the password hash itself in the database, why not the salt too?
In fact, many common password hashing schemes don't use a separate database column for the salt at all, they just store it as part of the hash string, e.g. as method$salt$hash
, where method
identifies the hashing method used, salt
and hash
are the salt and hash values encoded using some suitable scheme (e.g. base64) and $
is just an arbitrary separator character that doesn't occur in the encoded salt or hash.