
PostgreSQL, also known as Postgres, is an open-source relational database management system that stores data in tables made up of rows and columns. Users can define, manipulate, control, and query data using Structured Query Language (SQL). When running PostgreSQL on a Virtual Private Server (VPS), managing roles and permissions is essential for security and proper access control.
This guide demonstrates how to manage privileges and grant user permissions in PostgreSQL. You’ll learn how to create roles, assign privileges at different levels (database, schema, and table), use role inheritance, and secure your PostgreSQL installation on a VPS.
By the end of this tutorial, you’ll understand how PostgreSQL handles access control and how to safely manage users and privileges on a VPS setup.
LOGIN roles for database users and NOLOGIN roles as permission groups that other roles can inherit from.GRANT command to assign access at the database, schema, or table level so users only get what they truly need.pg_hba.conf for password-based authentication, enforce strong passwords, and restrict database access through firewalls.\du to view existing roles and \z to check who has access to what.To follow along with this tutorial, you will need:
Ubuntu server configured by following our Initial Server Setup for Ubuntu guide. Your server should have a non-root user with sudo permissions and a basic firewall.
PostgreSQL installed on your server. Complete Step 1 of our How To Install and Use PostgreSQL on Ubuntu tutorial.
With your environment prepared and Postgres running, you can begin learning about how PostgreSQL handles permissions for databases.
PostgreSQL manages permissions through roles. A role can represent a database user, a group of users, or both. Unlike traditional Unix-style permissions, PostgreSQL doesn’t distinguish between users and groups—roles can function as either.
When you install PostgreSQL, it creates a default role called postgres with superuser privileges. The installation also sets up peer authentication, which associates PostgreSQL roles with matching Unix/Linux system accounts. If a role exists in PostgreSQL, a Unix/Linux username with the same name can sign in as that role.
Note: In PostgreSQL, the terms “role” and “user” are often used interchangeably. The CREATE USER command is actually an alias for CREATE ROLE with the LOGIN privilege enabled by default.
While PostgreSQL uses “role” as the primary term, there’s a practical distinction:
Roles without LOGIN privilege: These roles are typically used to group together sets of permissions that can then be granted to various users, making it easier to manage access control at scale.
Roles with LOGIN privilege: These roles are essentially the actual database users who can authenticate and initiate a connection to PostgreSQL, allowing them to perform actions based on their granted permissions.
Superuser roles: Superusers possess unrestricted privileges within the PostgreSQL instance. They can override any permission checks and perform administrative tasks that regular roles cannot.
The default postgres role is a superuser with full database access. For production environments, create specific roles with only the privileges needed for each application or user.
Before we start managing roles, make sure that PostgreSQL is running and that you have access to its administrative interface before proceeding with role management.
Start PostgreSQL with:
sudo systemctl start postgresql.service
Verify it’s running:
sudo systemctl status postgresql.service
Look for “active (running)” in the output. If the service isn’t running, you’ll see “inactive” and need to troubleshoot the startup logs.
PostgreSQL uses peer authentication by default, which means your system username must match your PostgreSQL role name. Switch to the postgres system account:
sudo -i -u postgres
Then connect to PostgreSQL:
psql
You’ll see the postgres=# prompt, indicating you’re connected as the postgres superuser role. From here, you can execute SQL commands and PostgreSQL meta-commands (those starting with \).
List all roles with the \du meta-command:
\du
Meta-commands (starting with \) are processed by psql, not sent to PostgreSQL. The output shows role names, attributes, and group memberships:
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
A fresh installation includes only the postgres superuser role. The “Member of” column shows group role memberships, which we’ll cover later.
Create roles using CREATE ROLE for group roles or CREATE USER for login-capable roles. Choose based on whether the role needs to connect to the database.
Use CREATE ROLE to create a role that cannot log in (suitable for group roles):
CREATE ROLE role_name;
By default, roles created this way have NOLOGIN, meaning they can’t connect to the database. They’re ideal for organizing permissions that multiple roles will inherit.
Create a group role:
CREATE ROLE demo_role;
Verify it was created:
\du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
demo_role | Cannot login | {}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
The “Cannot login” attribute confirms demo_role is a group role, not a user role.
Note: Roles created with CREATE ROLE default to NOLOGIN, meaning they cannot connect to the database. They function as group roles for organizing permissions.
Use the command createuser to create roles from the command line without entering psql. This is useful for automation scripts or when you prefer command-line workflows.
Exit psql:
\q
Switch to the postgres system account:
- sudo -i -u postgres
Create a role interactively:
createuser --interactive
The --interactive flag prompts for role attributes. Answer the prompts:
Enter name of role to add: test_user
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n
Answering “n” to all creates a basic role with login privileges but no special permissions—perfect for application users.
Return to psql to verify:
psql
Compare the roles:
\du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
demo_role | Cannot login | {}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
test_user | | {}
test_user has no attributes listed, which means it can log in (unlike demo_role) but has no special privileges. This is the default for roles created with createuser.
Specify privileges at creation time using the WITH clause. This is more secure than creating a role and modifying it later, as it ensures the role starts with the correct permissions.
To set specific privileges as you create a role, use the following pattern:
CREATE ROLE role_name WITH privilege_options;
The WITH clause allows you to specify multiple attributes when creating the role, such as LOGIN, SUPERUSER, CREATEDB, or PASSWORD. You can combine multiple options separated by spaces.
View all available options:
\h CREATE ROLE
The \h meta-command displays help information for SQL commands. This shows you all available options for CREATE ROLE, including privilege settings and their descriptions.
| Privilege Option | Description |
|---|---|
LOGIN / NOLOGIN |
Grants or denies the ability for the role to log in and initiate a database connection session. |
SUPERUSER / NOSUPERUSER |
Assigns full administrative privileges, allowing the role to bypass all permission checks and manage all objects. |
CREATEDB / NOCREATEDB |
Allows or prevents the role from creating new databases within the PostgreSQL server instance. |
CREATEROLE / NOCREATEROLE |
Permits or restricts the role from creating or deleting other PostgreSQL roles, useful for administrative delegation. |
PASSWORD 'password' |
Sets the authentication password for the role, required especially when the role is granted LOGIN privileges. |
INHERIT / NOINHERIT |
Determines whether the role automatically inherits privileges from roles it is a member of, affecting access control schemes. |
Create a role with login privileges:
CREATE ROLE demo_role WITH LOGIN;
This creates a role that can connect to the database. The LOGIN attribute is required for roles that need to authenticate and establish database sessions.
Alternatively, you can use CREATE USER, which automatically grants LOGIN:
CREATE USER role_name;
The CREATE USER command is a convenience alias that automatically includes the LOGIN privilege, making it faster to create roles that can connect to the database.
This is equivalent to CREATE ROLE role_name WITH LOGIN;.
Modify existing roles with ALTER ROLE without dropping and recreating them. This is essential when security requirements change or you need to adjust permissions.
Change role attributes:
ALTER ROLE role_name WITH attribute_options;
You can change any attribute: LOGIN, NOLOGIN, CREATEDB, CREATEROLE, PASSWORD, etc.
Example: Convert a user role to a group role by removing login privileges:
ALTER ROLE demo_role WITH NOLOGIN;
Verify the change:
\du demo_role
You’ll see “Cannot login” in the attributes. To restore login capability:
ALTER ROLE demo_role WITH LOGIN;
Common use cases:
ALTER ROLE username WITH NOLOGIN;ALTER ROLE developer WITH CREATEDB;ALTER ROLE username WITH PASSWORD 'new_password';ALTER ROLE app_user WITH NOCREATEROLE;Remove roles with DROP ROLE, but only after transferring ownership of any objects they own.
Drop a role:
DROP ROLE role_name;
PostgreSQL prevents dropping roles that own database objects. If you see “cannot be dropped because some objects depend on it”, transfer ownership first:
ALTER TABLE table_name OWNER TO new_owner;
ALTER SEQUENCE sequence_name OWNER TO new_owner;
-- Repeat for all owned objects
Then drop the role. For scripts, use IF EXISTS to avoid errors if the role doesn’t exist:
DROP ROLE IF EXISTS role_name;
Note: You cannot drop a role that owns database objects (tables, sequences, functions, etc.). Transfer ownership to another role first using ALTER TABLE ... OWNER TO new_owner; or use REASSIGN OWNED BY old_role TO new_role; to transfer all objects at once.
Privileges control what actions roles can perform on database objects. By default, only the object owner and superusers can access newly created objects. Grant privileges to other roles to allow access.
PostgreSQL privileges control specific operations on database objects. Choose the minimum privileges needed for each role’s purpose.
Table-level privileges:
SELECT: Read data from tables. Required for queries, reporting, and read-only access.INSERT: Add new rows. Required for applications that create records.UPDATE: Modify existing rows. Required for applications that edit data.DELETE: Remove rows. Use carefully—this allows permanent data removal.TRUNCATE: Remove all rows instantly. More dangerous than DELETE—cannot be rolled back.REFERENCES: Create foreign key constraints pointing to the table. Needed when creating relationships.TRIGGER: Create triggers on the table. Usually only needed for advanced database logic.ALL PRIVILEGES: Grants all applicable privileges. Convenient but less secure—grant specific privileges when possible.Schema-level privileges:
USAGE: Access objects in the schema. Required before granting table privileges.CREATE: Create new objects (tables, sequences, etc.) in the schema. Grant carefully—allows creating arbitrary objects.Database-level privileges:
CONNECT: Connect to the database. Required for any database access.CREATE: Create schemas in the database. Usually only needed for setup scripts.TEMPORARY: Create temporary tables. Rarely needed for application roles.Warning: Granting ALL PRIVILEGES or DELETE/TRUNCATE on production tables can lead to accidental data loss. Use specific privileges and test in development first.
Grant privileges on specific tables when you need fine-grained control or when roles only need access to certain tables.
Grant a specific privilege:
GRANT privilege_type ON table_name TO role_name;
Create a sample table:
CREATE TABLE demo (
name varchar(25),
id serial,
start_date date
);
Note: The serial data type automatically creates a sequence (demo_id_seq) for auto-incrementing integer values.
Grant UPDATE only to demo_role:
GRANT UPDATE ON demo TO demo_role;
This allows demo_role to modify existing rows but not read, insert, or delete. This demonstrates granular permissions, which are useful for audit roles or restricted access scenarios.
Grant all privileges to test_user:
GRANT ALL ON demo TO test_user;
ALL grants SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, and TRIGGER. Use this sparingly—it’s convenient but less secure than specific privileges.
Warning: Granting privileges to PUBLIC applies to all current and future roles. This is rarely appropriate in production:
GRANT INSERT ON demo TO PUBLIC; -- Avoid in production
Use PUBLIC only for truly public data (like read-only reference tables) and always revoke it if you granted it by mistake:
REVOKE INSERT ON demo FROM PUBLIC;
Audit privileges with \z to see exactly who has access to what. This is essential for troubleshooting permission errors and verifying security.
View privileges on a table:
\z table_name
For more detail (including column-level privileges and row security policies):
\z+
Example output for the demo table:
\z demo
Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+-------------+----------+----------------------------+-------------------+----------
public | demo | table | postgres=arwdDxt/postgres +| |
| | | demo_role=w/postgres +| |
| | | test_user=arwdDxt/postgres+| |
| | | =a/postgres | |
public | demo_id_seq | sequence | | |
(2 rows)
Privilege code reference:
a = INSERTr = SELECT (read)w = UPDATE (write)d = DELETED = TRUNCATEx = REFERENCESt = TRIGGERIn the example above:
postgres=arwdDxt means the postgres role has all privileges (a, r, w, d, D, x, t)demo_role=w means demo_role only has UPDATE (w)test_user=arwdDxt means test_user has all privileges=a means PUBLIC has INSERT (a) privilegeNote: The format is role_name=privileges/grantor. The + indicates that there are additional privileges on the following lines.
Use schema-level grants when you need to grant access to multiple tables at once or allow roles to create objects in a schema.
Grant schema privileges:
GRANT privilege_type ON SCHEMA schema_name TO role_name;
Schema privileges affect all objects within that schema. Common use cases:
Example: Grant USAGE and CREATE on the public schema:
GRANT USAGE, CREATE ON SCHEMA public TO demo_role;
USAGE: Required to access objects in the schema. You must grant this before granting table privileges.CREATE: Allows creating new tables, sequences, and other objects. Grant carefully—this allows creating arbitrary objects.Warning: Granting CREATE on a schema allows the role to create any object type (tables, sequences, functions, etc.). In shared schemas, consider using dedicated schemas per application or restricting CREATE to specific roles.
When to use schema vs table grants:
ALTER DEFAULT PRIVILEGES).Use database-level grants to control connection access and schema creation. These are the broadest privileges and should be granted carefully.
Grant database privileges:
GRANT privilege_type ON DATABASE database_name TO role_name;
Database privileges:
CONNECT: Required to connect to the database. Without this, the role cannot establish a connection, even if it has table privileges.CREATE: Allows creating new schemas. Usually only needed for setup scripts or developers.TEMPORARY: Allows creating temporary tables. Rarely needed for application roles.Example: Allow a role to connect and create schemas:
GRANT CONNECT, CREATE ON DATABASE postgres TO demo_role;
Important: CONNECT is required for any database access. If a role can’t connect, check this first:
\du role_name -- Check if role exists
\z database_name -- Check CONNECT privilege (not directly viewable with \z)
To verify CONNECT privilege, query the system catalog:
SELECT has_database_privilege('role_name', 'database_name', 'CONNECT');
Note: Database privileges don’t grant access to tables. You still need to grant schema USAGE and table privileges separately. The hierarchy is: Database → Schema → Table.
Create a read-only role when you need to grant access for reporting, monitoring, or analytics without allowing data modification. This follows the principle of least privilege and prevents accidental data changes.
Complete workflow for a read-only application role:
-- 1) Create a login role with a strong password
CREATE ROLE app_ro WITH LOGIN PASSWORD 'strong_password';
-- 2) Grant permission to connect to the desired database
GRANT CONNECT ON DATABASE postgres TO app_ro;
-- 3) Allow usage of the target schema
GRANT USAGE ON SCHEMA public TO app_ro;
-- 4) Grant read (SELECT) rights on all current tables in the schema
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_ro;
-- 5) Ensure read (SELECT) rights are automatically granted on new tables in the schema
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_ro;
Why each step matters:
CONNECT privilege is required to establish a connection to the databaseUSAGE on the schema is required before you can grant table privilegesSELECT on all existing tablesSELECT to this role (critical for production)Verify the setup:
\du app_ro -- Check role exists and has LOGIN
\z public.your_table_name -- Verify SELECT privilege on a specific table
ALTER DEFAULT PRIVILEGES only has an effect on objects that are created after the command is executed. When new app tables are created, they will automatically grant app_ro the SELECT privilege. However, if you are restoring from a backup, you might need to run step 4 again in order to assign privileges to restored tables.
Create a role for a web application that needs to read and write data, but shouldn’t be able to drop tables or modify schema structure.
Complete workflow for a typical web application:
-- 1) Create the application role with login and password
CREATE ROLE webapp WITH LOGIN PASSWORD 'secure_password_here';
-- 2) Grant database connection
GRANT CONNECT ON DATABASE your_database TO webapp;
-- 3) Grant schema usage (required before table privileges)
GRANT USAGE ON SCHEMA public TO webapp;
-- 4) Grant read/write privileges on all existing tables
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO webapp;
-- 5) Grant usage on all existing sequences (needed for serial/identity columns)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO webapp;
-- 6) Set default privileges for future tables and sequences
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO webapp;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO webapp;
What this role can do:
SELECT)INSERT)UPDATE)DELETE)What this role cannot do:
DROP TABLE requires ownership or DROP privilege)CREATE on schema)ALTER TABLE requires ownership)TRUNCATE privilege)This is the standard setup for most web applications (Django, Rails, Laravel, etc.).
Note: If your application uses database migrations (like Django or Rails), you’ll need a separate migration role with CREATE privileges on the schema. Run migrations as that role, then use the webapp role for normal application operations.
Remove privileges with REVOKE when security requirements change or roles no longer need access. This is the inverse of GRANT and follows the same syntax.
Revoke a specific privilege:
REVOKE privilege_type ON object_name FROM role_name;
Example: Remove INSERT from PUBLIC (if you mistakenly granted it):
REVOKE INSERT ON demo FROM PUBLIC;
After this, only roles with explicitly granted INSERT privileges can add rows.
Revoke all privileges:
REVOKE ALL ON demo FROM role_name;
This removes all privileges the role had on the object. Use this when completely removing a role’s access.
Note: Revoking privileges doesn’t affect objects the role owns. To remove ownership, transfer it first with ALTER TABLE ... OWNER TO new_owner;.
Role inheritance allows roles to automatically inherit privileges from the group roles to which they belong. This simplifies permission management by organizing roles into groups and granting privileges to the group rather than individual roles.
By default, roles inherit privileges from the group roles to which they belong. When you add a role to a group, it automatically gains the group’s privileges (if INHERIT is enabled, which is the default).
Use group roles when:
CREATE on a schema)Avoid group roles when:
Create group roles with CREATE ROLE (which defaults to NOLOGIN). Group roles organize permissions that multiple roles will share.
Create a group role:
CREATE ROLE temporary_users;
Since CREATE ROLE defaults to NOLOGIN, this role can’t connect to the database, which is perfect for a group role.
Add members to the group:
GRANT temporary_users TO demo_role;
GRANT temporary_users TO test_user;
The syntax GRANT group_role TO member_role adds roles as members. Both demo_role and test_user now inherit any privileges granted to temporary_users.
Verify memberships:
\du
The “Member of” column shows group memberships. You should see {temporary_users} for both demo_role and test_user.
List of roles
Role name | Attributes | Member of
-----------------+------------------------------------------------------------+-------------------
demo_role | | {temporary_users}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
temporary_users | Cannot login | {}
test_user | | {temporary_users}
Use SET ROLE to temporarily act as a group role when creating objects that should be owned by the group. This is useful for shared resources or when you want objects owned by a group rather than an individual role.
Switch to a group role:
SET ROLE temporary_users;
Any objects created while this role is active are owned by temporary_users, not your original role:
CREATE TABLE hello (
name varchar(25),
id serial,
start_date date
);
Verify ownership:
\d
List of relations
Schema | Name | Type | Owner
--------+--------------+----------+-----------------
public | demo | table | postgres
public | demo_id_seq | sequence | postgres
public | hello | table | temporary_users
public | hello_id_seq | sequence | temporary_users
(4 rows)
The hello table is owned by temporary_users, not your original role. Return to your original role:
RESET ROLE;
Note: SET ROLE is different from inheritance. With inheritance (default), you automatically have the group’s privileges. With SET ROLE, you temporarily become the group role for object creation purposes.
Inheritance is enabled by default. Roles automatically use privileges from group roles they belong to without needing to SET ROLE.
Verify inheritance is enabled:
SELECT rolname, rolinherit FROM pg_roles WHERE rolname = 'test_user';
If rolinherit is t (true), inheritance is enabled. To explicitly enable it:
ALTER ROLE test_user INHERIT;
With inheritance enabled, test_user can use the privileges granted to temporary_users automatically. For example, if you grant SELECT on a table to temporary_users, test_user can query that table without any additional grants.
Note: INHERIT is the default. Use NOINHERIT only if you want a role to explicitly use SET ROLE to access group privileges. This is rare and usually only needed for security auditing scenarios where you need to track which specific role performed an action.
Transfer ownership of all objects before dropping a group role. PostgreSQL prevents dropping roles that own database objects.
Transfer ownership:
ALTER TABLE hello OWNER TO demo_role;
For multiple objects, use REASSIGN OWNED to transfer all at once:
REASSIGN OWNED BY temporary_users TO demo_role;
This transfers ownership of all tables, sequences, functions, and other objects owned by temporary_users to demo_role.
Verify the role no longer owns objects:
\d
Once all objects are transferred, drop the group role:
DROP ROLE temporary_users;
Note: Dropping a group role doesn’t delete its member roles. The members (demo_role and test_user) remain but lose access to the group’s privileges. They keep any privileges granted directly to them.
Securing PostgreSQL on a VPS requires configuring authentication, network access, and firewall rules. These steps protect your database from unauthorized access.
Configure authentication in pg_hba.conf to control who can connect and how they authenticate. This file determines connection rules for your PostgreSQL instance.
View the current configuration:
sudo -u postgres cat /etc/postgresql/*/main/pg_hba.conf
Note: The path may vary by PostgreSQL version. Common locations include /etc/postgresql/[version]/main/pg_hba.conf on Ubuntu.
The file contains rules in this format: TYPE DATABASE USER ADDRESS METHOD. Rules are processed from top to bottom, so order matters.
Authentication methods:
peer: Uses the operating system username (default for local connections). Requires system username to match PostgreSQL role name.md5: Password authentication using MD5 hashing. Legacy method, still supported but less secure.scram-sha-256: Modern password authentication (recommended). More secure than md5.trust: No authentication required. Never use in production—only for local development.Edit the configuration:
sudo nano /etc/postgresql/*/main/pg_hba.conf
For production, configure password authentication for local connections:
# TYPE DATABASE USER ADDRESS METHOD
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
These lines require password authentication (scram-sha-256) for:
127.0.0.1/32)::1/128)The /32 and /128 are CIDR notations specifying single IP addresses.
Reload PostgreSQL to apply changes:
sudo systemctl reload postgresql
reload applies changes without stopping the service, preserving active connections. Use restart only if reload doesn’t work.
Set passwords for roles that connect remotely or use password authentication. Passwords are stored encrypted in PostgreSQL’s system catalogs.
Set a password:
ALTER ROLE role_name WITH PASSWORD 'secure_password';
For better security, use the interactive \password command to avoid exposing the password in shell history:
\password role_name
If you omit role_name, it sets the password for the currently connected role. The command prompts twice for confirmation and doesn’t display the password on screen.
Note: Use strong, unique passwords for production databases. Consider using a password manager to generate and securely store them.
Restrict network access to PostgreSQL using firewall rules. By default, PostgreSQL listens only on 127.0.0.1 (localhost), which prevents remote connections.
Check which interfaces PostgreSQL is listening on:
sudo netstat -plnt | grep postgres
Look for 127.0.0.1:5432 in the output, which confirms PostgreSQL is only accepting local connections. If you see 0.0.0.0:5432, PostgreSQL is listening on all interfaces.
If you need remote access (only enable if necessary):
Configure PostgreSQL to listen on specific interfaces:
sudo nano /etc/postgresql/*/main/postgresql.conf
Find and modify:
listen_addresses = 'localhost' # Change to '*' or specific IP
Options:
'localhost': Only local connections (default, most secure)'*': All interfaces (use with extreme caution)'192.168.1.100': Specific IP address (more secure than *)Add firewall rule to allow connections only from trusted IPs:
sudo ufw allow from trusted_ip_address to any port 5432
Replace trusted_ip_address with the actual client IP. This restricts access at the firewall level before PostgreSQL even sees the connection attempt.
Warning: Only enable remote access if absolutely necessary. Always use:
scram-sha-256 authentication)Never expose PostgreSQL to the public internet without proper security measures.
Prevent brute-force attacks by limiting connection attempts. Configure pg_hba.conf with connection limits or use a firewall tool like fail2ban.
PostgreSQL uses peer authentication by default for local connections, requiring your system username to match your PostgreSQL role name. To connect as a different role, use password authentication through the network interface.
Set a password for the role:
\password test_user
The command prompts twice for confirmation and doesn’t display the password. Exit psql:
\q
Connect using explicit parameters to override peer authentication:
psql -U user_name -d database_name -h 127.0.0.1 -W
Parameters:
-U: Username to connect as-d: Database name-h 127.0.0.1: Connect through the loopback interface (enables password authentication)-W: Prompt for passwordExample: Connect as test_user:
psql -U test_user -d postgres -h 127.0.0.1 -W
Note: Using -h 127.0.0.1 connects through the network interface, which bypasses peer authentication and uses password authentication instead. This is necessary when your system username doesn’t match your PostgreSQL role name.
Permission errors in PostgreSQL typically arise from missing privileges, incorrect authentication configurations, or incorrect role attributes. This section covers the most common issues and their solutions.
Problem: You see FATAL: role "username" does not exist or password authentication failed when trying to connect.
Solution: Check three things in order:
Verify the role exists and has LOGIN privilege:
\du username
If the role doesn’t exist, create it:
CREATE ROLE username WITH LOGIN PASSWORD 'secure_password';
If it exists but shows “Cannot login”, enable login:
ALTER ROLE username WITH LOGIN;
Check pg_hba.conf allows the connection:
sudo -u postgres cat /etc/postgresql/*/main/pg_hba.conf | grep -v "^#"
Ensure there’s a matching rule for your connection type. For password authentication:
host all username 127.0.0.1/32 scram-sha-256
Reload PostgreSQL after configuration changes:
sudo systemctl reload postgresql
Note: Password authentication must use scram-sha-256 for secure environments; never use trust in production.
permission denied for tableProblem: Your application connects successfully but queries fail with permission denied for table table_name.
Root cause: The role lacks explicit privileges on the table or schema. PostgreSQL defaults to restrictive permissions—roles can’t read or write unless explicitly granted.
Solution: Grant the required privileges:
-- Grant schema usage (required first)
GRANT USAGE ON SCHEMA public TO username;
-- Grant table privileges
GRANT SELECT, INSERT, UPDATE ON table_name TO username;
For multiple tables, grant on all tables in the schema:
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO username;
For future tables, set default privileges so new tables automatically grant access:
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE ON TABLES TO username;
Note: ALTER DEFAULT PRIVILEGES only affects objects created after the command runs. After restoring from a backup or migrating databases, you may need to re-grant privileges on existing tables.
Error: ERROR: permission denied to create database or ERROR: permission denied to create table
Diagnosis:
The role lacks the CREATEDB attribute or CREATE privilege on the schema.
Fix:
ALTER ROLE username WITH CREATEDB;
GRANT CREATE ON SCHEMA public TO username;
To verify:
\du username
Grant schema-level CREATE carefully—roles can create arbitrary objects inside the schema. For shared environments, use dedicated namespaces.
Why this happens:
PostgreSQL, by default, listens only on the local interface (127.0.0.1). If remote connections fail, it’s almost always due to configuration in postgresql.conf, firewall rules, or missing host entries in pg_hba.conf.
Solution:
Edit PostgreSQL’s main configuration:
sudo nano /etc/postgresql/*/main/postgresql.conf
Ensure this line is uncommented:
listen_addresses = '*'
In pg_hba.conf, add a secure host entry:
host all all trusted_ip/32 scram-sha-256
Allow access in the firewall:
sudo ufw allow from trusted_ip to any port 5432
Reload PostgreSQL:
sudo systemctl reload postgresql
Always restrict port 5432 to specific trusted IPs—never expose it publicly.
Symptom: Roles appear valid, but queries fail with “permission denied” or missing ownership.
Cause: Object ownership isn’t automatically preserved during dump/restore unless roles are recreated first.
Resolution:
Before restoring, recreate all roles:
CREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password';
Restore the database dump using a role with superuser privileges:
psql -U postgres -d target_db -f backup.sql
Reassign ownership:
REASSIGN OWNED BY old_owner TO app_user;
Use REASSIGN OWNED carefully—it updates ownership for all dependent objects of a role.
Root cause: The ALTER DEFAULT PRIVILEGES command wasn’t executed by the object owner.
Solution: Re-run the command while logged in as the schema owner:
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_ro;
Then verify privileges:
\z public.*
Default privileges are owned by the creator role, not superusers. Run as the same role that owns the schema.
Typical causes:
CONNECT privilege hasn’t been granted.Fix:
GRANT CONNECT ON DATABASE db_name TO role_name;
ALTER TABLE table_name DISABLE ROW LEVEL SECURITY;
For production, enable RLS only with explicit policies in place. Misconfiguration can silently block queries for non-superusers.
Why: SSL negotiation or incorrect certificate paths in postgresql.conf.
How to verify: Check PostgreSQL logs:
sudo journalctl -u postgresql | grep SSL
Ensure configuration points to valid certs:
ssl = on
ssl_cert_file = '/etc/ssl/certs/ssl-cert.pem'
ssl_key_file = '/etc/ssl/private/ssl-cert.key'
PostgreSQL’s SSL support enhances security but adds slight latency. Tune connection pooling for performance-sensitive environments.
Use \du to audit role attributes and memberships.
Use \z to inspect privileges on objects.
Review pg_hba.conf for missing or misordered rules.
Check the PostgreSQL logs (/var/log/postgresql/postgresql-*.log) for precise authentication reasons.
After any configuration change, reload PostgreSQL using:
sudo systemctl reload postgresql
These scenarios represent the most common, high-impact permission and connectivity challenges developers face in PostgreSQL on VPS or cloud setups. By diagnosing issues through privilege inspection and configuration hierarchy, you can maintain secure, stable, and predictable access control across environments.
In PostgreSQL, “role” and “user” are essentially the same concept. The CREATE USER command is an alias for CREATE ROLE WITH LOGIN. The primary distinction is practical: roles with LOGIN privilege can connect to the database (function as users), while roles without LOGIN function as groups for organizing permissions.
Grant SELECT privilege on specific tables:
GRANT SELECT ON table_name TO role_name;
For all tables in a schema:
GRANT SELECT ON ALL TABLES IN SCHEMA schema_name TO role_name;
For future tables in a schema:
ALTER DEFAULT PRIVILEGES IN SCHEMA schema_name GRANT SELECT ON TABLES TO role_name;
Use the REVOKE command:
REVOKE privilege_type ON object_name FROM role_name;
Revoke all privileges:
REVOKE ALL ON object_name FROM role_name;
Use the principle of least privilege: Grant only the minimum privileges necessary for each role.
Organize with group roles: Create group roles for common permission sets and add individual roles as members.
Use strong passwords: Set secure passwords for all roles that can log in, especially in production.
Configure authentication properly: Use scram-sha-256 or md5 authentication in pg_hba.conf instead of trust for production.
Restrict network access: Use firewall rules to limit PostgreSQL connections to trusted IP addresses.
Regular audits: Periodically review role privileges using \du and \z to ensure they match your security requirements.
Configure postgresql.conf: Set listen_addresses to include your server’s IP or * (use with caution).
Update pg_hba.conf: Add a host entry with secure authentication:
host database_name role_name client_ip/32 scram-sha-256
Set a strong password: Use ALTER ROLE to set a secure password.
Configure firewall: Allow connections only from trusted IP addresses:
sudo ufw allow from trusted_ip to any port 5432
Reload PostgreSQL: Apply configuration changes:
sudo systemctl reload postgresql
Role inheritance allows a role to automatically receive privileges from group roles it’s a member of. When you add a role to a group using GRANT group_role TO member_role, the member role inherits the group’s privileges (if INHERIT is enabled, which is the default).
Example:
CREATE ROLE developers;
GRANT CREATE ON SCHEMA public TO developers;
GRANT developers TO app_user;
The app_user role can now create objects in the public schema because it inherits privileges from developers.
View all roles:
\du
View privileges on a specific object:
\z object_name
View detailed role information:
SELECT * FROM pg_roles WHERE rolname = 'role_name';
View privileges granted to a role:
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE grantee = 'role_name';
You now understand how to manage roles and permissions in PostgreSQL on a VPS. These skills enable you to provide applications with the necessary database access while maintaining security and separation between different services.
Key concepts covered include creating and modifying roles, granting and revoking privileges at different levels, using role inheritance for efficient permission management, and securing your PostgreSQL installation on a VPS.
For more information about PostgreSQL and related topics, consider these resources:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Former Senior Technical Writer at DigitalOcean, specializing in DevOps topics across multiple Linux distributions, including Ubuntu 18.04, 20.04, 22.04, as well as Debian 10 and 11.
Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Excellent, easy-to-follow intro to roles and permissions in PostgreSQL. Thanks Justin.
A small note. On Ubuntu 14.04, after logging in as ‘test_user’, then reverting to the admin session and typing sudo su - postgres from the command line, I found I was asked for the postgres password, which I didn’t set / didn’t know. Problem solved by opening a new terminal and entering the same command - then I was asked for my standard Ubuntu password.
Hi, I’m trying to create a user with permissions to create a database. I’m logged in as the deploy user and I’m able to create the user ‘deploy’
However, when I try to alter the role and give it CREATEDB permissions with the command:
ALTER ROLE deploy WITH CREATEDB
and then check on the changed permissions with \du, nothing has happened. The ‘deploy’ user is there, but it has no CREATEDB permissions.
I also tried this as a root user, with the same result.
I also tried to create the user from the commandline with:
createuser deploy
according to this tutorial I should get these questions:
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n
but I didn’t, the user gets created and I’m back at the prompt.
at the prompt I’m also seeing:
psql (9.4beta3)
Does that mean my version of Postgres is 9.4? That’s odd, because I installed version 9.3
thanks for your help,
Anthony
Thanks! Didn’t know a default user is created and its name is postgres
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.