[{"data":1,"prerenderedAt":1648},["ShallowReactive",2],{"authors":3,"\u002Fblog\u002Fsecurely-self-hosting-postgresql":26,"\u002Fblog\u002Fsecurely-self-hosting-postgresql-surround":1641},[4,16],{"id":5,"avatar":6,"bio":7,"extension":8,"github":9,"meta":10,"name":11,"role":12,"stem":13,"website":14,"__hash__":15},"authors\u002Fauthors\u002Fsagar-kapoor.yml","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F69609200?v=4",null,"yml","sagar-kap",{},"Sagar Kapoor","Chief Executive Officer (CEO)","authors\u002Fsagar-kapoor","https:\u002F\u002Fsagarkapoor.eu","NsLNClfs_S-SNHFEnyR6Vq_7zEc1Xxl7QLoKrk_DB6s",{"id":17,"avatar":18,"bio":7,"extension":8,"github":19,"meta":20,"name":21,"role":22,"stem":23,"website":24,"__hash__":25},"authors\u002Fauthors\u002Fsomraj-saha.yml","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F31373860?v=4","jarmos-san",{},"Somraj Saha","Chief Technology Officer (CTO)","authors\u002Fsomraj-saha","https:\u002F\u002Fjarmos.dev","mDAq8GgPZv9H8en0XHAraq3aYi_v8t2ULWGqp4cp3zY",{"id":27,"title":28,"author":29,"body":30,"category":1630,"cover":1631,"date":1632,"description":1633,"extension":1634,"meta":1635,"navigation":1636,"path":1637,"seo":1638,"stem":1639,"__hash__":1640},"blog\u002Fblog\u002Fsecurely-self-hosting-postgresql.md","Securely Self-Hosting PostgreSQL: Configuration, Backups, and Best Practices","somraj-saha",{"type":31,"value":32,"toc":1590},"minimark",[33,45,54,63,66,71,80,83,88,91,106,110,113,121,125,138,147,154,158,161,172,175,179,185,188,192,197,225,228,269,272,366,370,373,402,416,420,427,445,452,520,523,532,535,544,548,555,562,583,590,617,627,630,634,640,643,647,653,669,673,683,701,705,708,711,761,768,798,807,811,822,825,834,841,861,866,869,878,881,894,897,913,917,924,927,930,938,941,944,953,956,967,985,994,1002,1009,1015,1024,1031,1034,1042,1072,1087,1090,1094,1100,1118,1122,1125,1139,1142,1146,1149,1152,1156,1163,1173,1179,1182,1186,1195,1206,1232,1244,1248,1251,1254,1368,1371,1375,1385,1388,1395,1404,1407,1451,1469,1473,1476,1485,1489,1492,1576,1580,1583,1586],[34,35,36,37,44],"p",{},"At ",[38,39,43],"a",{"href":40,"rel":41},"https:\u002F\u002Fweburz.com",[42],"nofollow","Weburz",", we've felt the pinch that many growing tech\ncompanies experience: managed database bills that start small, but scale into a\nheavy, recurring expense for marginal operational benefits. As our\ninfrastructure needs grew, relying exclusively on third-party cloud providers\nstopped making financial sense.",[34,46,47,48,53],{},"At the same time, we frequently ran into a wall of fear online. Spend five\nminutes browsing tech forums, and database administration is painted as a\nterrifying, high-stakes tightrope walk. You'll read endless horror stories about\ncatastrophic downtime, misconfigured nodes, and nightmare recovery scenarios.\nBut our experience at Weburz proved otherwise: if done right, maintaining a\nself-hosted ",[38,49,52],{"href":50,"rel":51},"https:\u002F\u002Fwww.postgresql.com",[42],"PostgreSQL"," server is not nearly as\nscary as the internet portrays it to be. With the right blueprints, automation,\nand best practices, it is entirely manageable.",[34,55,56,57,62],{},"Of course, moving away from a managed environment also meant tackling valid\nprivacy and security concerns. We couldn't just throw a database onto the public\ninternet and hope for the best. To solve this, we locked down our infrastructure\nby hosting it safely behind robust on-premise firewalls and modern zero-trust\nnetworking tools like ",[38,58,61],{"href":59,"rel":60},"https:\u002F\u002Ftailscale.com",[42],"Tailscale",", giving us total peace\nof mind over our data perimeter.",[34,64,65],{},"In this article, we are pulling back the curtain on how we solved these exact\nchallenges at Weburz. Follow along as we share our comprehensive, step-by-step\nguidelines on how we successfully self-host our PostgreSQL database\nserver-cutting costs, reclaiming control, and keeping our data secure.",[67,68,70],"h2",{"id":69},"prerequisites-and-infrastructure-requirements","Prerequisites and Infrastructure Requirements",[34,72,73,74,79],{},"Before we dive into the installation commands, we need to lay a solid\nfoundation. At Weburz, we learned early on that skipping proper infrastructure\nplanning leads to performance bottlenecks later. Because our infrastructure runs\non ",[38,75,78],{"href":76,"rel":77},"https:\u002F\u002Fazure.microsoft.com",[42],"Microsoft Azure",", setting up our self-hosted\nPostgreSQL server starts with selecting the right Azure Virtual Machine (VM)\nsize, storage tier, and configuring Azure networking correctly.",[34,81,82],{},"Here is the exact Azure infrastructure blueprint we use to get started:",[84,85,87],"h3",{"id":86},"_1-azure-vm-sizing-and-hardware-considerations","1. Azure VM Sizing and Hardware Considerations",[34,89,90],{},"PostgreSQL is notoriously efficient, but its performance heavily depends on your\nunderlying hardware-especially memory and disk throughput. When provisioning an\nAzure VM, we recommend:",[92,93,94,103],"ul",{},[95,96,97,98,102],"li",{},"Series Selection: Use General Purpose (e.g., Dv5 or Dasv5-series) or Memory\nOptimized (e.g., Ev5-series) VMs. Memory-optimized instances are fantastic\nbecause PostgreSQL relies heavily on caching data in RAM (",[99,100,101],"code",{},"shared_buffers","),\nwhich directly speeds up query execution.",[95,104,105],{},"vCPUs and RAM: A 2 vCPU \u002F 8 GB RAM instance is a great starting point for\nstandard workloads, but scale this up as your concurrent connections and\ndataset grow.",[84,107,109],{"id":108},"_2-azure-managed-disks-storage-is-crucial","2. Azure Managed Disks (Storage is Crucial)",[34,111,112],{},"Database operations involve frequent random reads and writes, meaning slow disks\nwill instantly bottleneck your application.",[92,114,115,118],{},[95,116,117],{},"Avoid Standard HDDs: Do not use Standard HDD storage for a production\ndatabase.",[95,119,120],{},"Premium SSDs \u002F Ultra Disks: We strictly use Azure Premium SSDs (or Ultra Disks\nfor heavy I\u002FO workloads) to ensure high IOPS (Input\u002FOutput Operations Per\nSecond) and low latency. Enabling Host Caching (Read-only for data disks) can\nalso significantly improve read performance.",[84,122,124],{"id":123},"_3-operating-system-choice","3. Operating System Choice",[34,126,127,128,133,134,137],{},"For consistency, stability, and long-term support, we standardize on Debian\nStable. ",[38,129,132],{"href":130,"rel":131},"https:\u002F\u002Fwww.debian.org",[42],"Debian"," provides rock-solid reliability, and\nits package managers (",[99,135,136],{},"apt",") make installing and patching PostgreSQL seamless.",[34,139,140,141,146],{},"To maintain strict environment consistency across our infrastructure at Weburz,\nwe use ",[38,142,145],{"href":143,"rel":144},"https:\u002F\u002Fdeveloper.hashicorp.com\u002Fpacker",[42],"HashiCorp Packer"," to build\nstandardized \"golden images\" running on Debian. This ensures every database\ninstance we spin up is pre-configured identically, drastically reducing\nconfiguration drift.",[34,148,149,153],{},[150,151,152],"strong",{},"NOTE",": Because diving deep into Packer requires a guide of its own, we will\nbe covering our automated image-building pipeline in detail in an upcoming blog\npost!",[84,155,157],{"id":156},"_4-azure-networking-and-security-integration","4. Azure Networking and Security Integration",[34,159,160],{},"At Weburz, we never expose our database server directly to the public internet.\nInstead, we lock down our network architecture within Azure:",[92,162,163,166,169],{},[95,164,165],{},"Virtual Network (VNet) & Subnets: Deploy your PostgreSQL VM within a private\nsubnet inside an Azure VNet, keeping it entirely isolated from external\ninbound traffic.",[95,167,168],{},"Network Security Groups (NSGs): Configure your NSG rules to block all inbound\ntraffic by default, only allowing explicitly trusted internal IPs or\napplication subnets.",[95,170,171],{},"Tailscale Integration: To connect our distributed application servers securely\nto this private Azure VM without complex VPN gateways or peering overhead, we\ninstall Tailscale. This routes all database traffic through an encrypted,\nzero-trust tunnel, ensuring absolute privacy whether our apps are running in\nother Azure regions, AWS, or on-premise. For further security, we only allow a\nselect group of IT Admins with access to the database servers using the VPN.",[34,173,174],{},"With your Azure VM provisioned, storage optimized, and network secured, you are\nready for the next step: installing and bootstrapping PostgreSQL.",[67,176,178],{"id":177},"installing-postgresql-on-your-server","Installing PostgreSQL on Your Server",[34,180,181,182,184],{},"With our Azure infrastructure and operating system foundation locked in, it is\ntime to get PostgreSQL up and running. At Weburz, we prefer a clean, native\ninstallation using the official PostgreSQL Global Development Group (PGDG) ",[99,183,136],{},"\nrepository rather than default OS package repositories. This ensures we get the\nlatest stable, performance-optimised versions of PostgreSQL directly from the\nmaintainers.",[34,186,187],{},"Here is th step-by-step process we follow to install PostgreSQL on our Debian\ngolden images:",[84,189,191],{"id":190},"_1-import-the-official-postgresql-repository","1. Import the Official PostgreSQL Repository",[34,193,194,196],{},[150,195,152],{},": On Debian, and especially in automated environments where user\ninteractivity is non-existent, we also add this variable at the top of the\nscript:",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-bash shiki shiki-themes github-dark","export DEBIAN_FRONTEND=noninteractive\n","bash","",[99,205,206],{"__ignoreMap":203},[207,208,211,215,219,222],"span",{"class":209,"line":210},"line",1,[207,212,214],{"class":213},"snl16","export",[207,216,218],{"class":217},"s95oV"," DEBIAN_FRONTEND",[207,220,221],{"class":213},"=",[207,223,224],{"class":217},"noninteractive\n",[34,226,227],{},"First, update your local package list and install the necessary prerequisites to\nsecurely fetch packages:",[198,229,231],{"className":200,"code":230,"language":202,"meta":203,"style":203},"sudo apt-get update\nsudo apt-get install --assume-yes curl ca-certificates gnupg\n",[99,232,233,246],{"__ignoreMap":203},[207,234,235,239,243],{"class":209,"line":210},[207,236,238],{"class":237},"svObZ","sudo",[207,240,242],{"class":241},"sU2Wk"," apt-get",[207,244,245],{"class":241}," update\n",[207,247,249,251,253,256,260,263,266],{"class":209,"line":248},2,[207,250,238],{"class":237},[207,252,242],{"class":241},[207,254,255],{"class":241}," install",[207,257,259],{"class":258},"sDLfK"," --assume-yes",[207,261,262],{"class":241}," curl",[207,264,265],{"class":241}," ca-certificates",[207,267,268],{"class":241}," gnupg\n",[34,270,271],{},"Next, import the official PostgreSQL signing key and add the repository to your\nsystem's sources list:",[198,273,275],{"className":200,"code":274,"language":202,"meta":203,"style":203},"sudo install --directory \u002Fetc\u002Fapt\u002Fkeyrings\ncurl --fail --silent --show-error --location https:\u002F\u002Fwww.postgresql.org\u002Fmedia\u002Fkeys\u002FACCC4CF8.asc \\\n  | sudo gpg --dearmor --output \u002Fetc\u002Fapt\u002Fkeyrings\u002Fpostgresql.gpg\necho \"deb [signed-by=\u002Fetc\u002Fapt\u002Fkeyrings\u002Fpostgresql.gpg] http:\u002F\u002Fapt.postgresql.org\u002Fpub\u002Frepos\u002Fapt $(lsb_release -cs)-pgdg main\" \\\n  | sudo tee \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fpgdg.list\n",[99,276,277,289,312,333,353],{"__ignoreMap":203},[207,278,279,281,283,286],{"class":209,"line":210},[207,280,238],{"class":237},[207,282,255],{"class":241},[207,284,285],{"class":258}," --directory",[207,287,288],{"class":241}," \u002Fetc\u002Fapt\u002Fkeyrings\n",[207,290,291,294,297,300,303,306,309],{"class":209,"line":248},[207,292,293],{"class":237},"curl",[207,295,296],{"class":258}," --fail",[207,298,299],{"class":258}," --silent",[207,301,302],{"class":258}," --show-error",[207,304,305],{"class":258}," --location",[207,307,308],{"class":241}," https:\u002F\u002Fwww.postgresql.org\u002Fmedia\u002Fkeys\u002FACCC4CF8.asc",[207,310,311],{"class":258}," \\\n",[207,313,315,318,321,324,327,330],{"class":209,"line":314},3,[207,316,317],{"class":213},"  |",[207,319,320],{"class":237}," sudo",[207,322,323],{"class":241}," gpg",[207,325,326],{"class":258}," --dearmor",[207,328,329],{"class":258}," --output",[207,331,332],{"class":241}," \u002Fetc\u002Fapt\u002Fkeyrings\u002Fpostgresql.gpg\n",[207,334,336,339,342,345,348,351],{"class":209,"line":335},4,[207,337,338],{"class":258},"echo",[207,340,341],{"class":241}," \"deb [signed-by=\u002Fetc\u002Fapt\u002Fkeyrings\u002Fpostgresql.gpg] http:\u002F\u002Fapt.postgresql.org\u002Fpub\u002Frepos\u002Fapt $(",[207,343,344],{"class":237},"lsb_release",[207,346,347],{"class":258}," -cs",[207,349,350],{"class":241},")-pgdg main\"",[207,352,311],{"class":258},[207,354,356,358,360,363],{"class":209,"line":355},5,[207,357,317],{"class":213},[207,359,320],{"class":237},[207,361,362],{"class":241}," tee",[207,364,365],{"class":241}," \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fpgdg.list\n",[84,367,369],{"id":368},"_2-install-postgresql","2. Install PostgreSQL",[34,371,372],{},"Once the repository is registered, update your package list again and install\nyour desired PostgreSQL version (for example, version 19):",[198,374,376],{"className":200,"code":375,"language":202,"meta":203,"style":203},"sudo apt-get update\nsudo apt-get install --assume-yes postgresql-19 postgresql-contrib-19\n",[99,377,378,386],{"__ignoreMap":203},[207,379,380,382,384],{"class":209,"line":210},[207,381,238],{"class":237},[207,383,242],{"class":241},[207,385,245],{"class":241},[207,387,388,390,392,394,396,399],{"class":209,"line":248},[207,389,238],{"class":237},[207,391,242],{"class":241},[207,393,255],{"class":241},[207,395,259],{"class":258},[207,397,398],{"class":241}," postgresql-19",[207,400,401],{"class":241}," postgresql-contrib-19\n",[34,403,404,407,408,411,412,415],{},[150,405,406],{},"TIP",": If you want to future-proof your installation or let the system pull\nthe absolute latest stable version automatically, you can substitute\n",[99,409,410],{},"postgresql-16"," with just ",[99,413,414],{},"postgresql",".",[84,417,419],{"id":418},"_3-verify-the-installation","3. Verify the Installation",[34,421,422,423,426],{},"By default, the installer automatically initializes a default database cluster\nand starts the PostgreSQL service as a ",[99,424,425],{},"systemd"," daemon. You can verify that the\nservice is running smoothly with:",[198,428,430],{"className":200,"code":429,"language":202,"meta":203,"style":203},"sudo systemctl status postgresql\n",[99,431,432],{"__ignoreMap":203},[207,433,434,436,439,442],{"class":209,"line":210},[207,435,238],{"class":237},[207,437,438],{"class":241}," systemctl",[207,440,441],{"class":241}," status",[207,443,444],{"class":241}," postgresql\n",[34,446,447,448,451],{},"You should see an active (",[99,449,450],{},"running",") status confirming that your server is\noperational like this:",[198,453,457],{"className":454,"code":455,"language":456,"meta":203,"style":203},"language-console shiki shiki-themes github-dark","● postgresql.service - PostgreSQL database server\n  Loaded: loaded (\u002Flib\u002Fsystemd\u002Fsystem\u002Fpostgresql.service; enabled)\n  Active: active (running) since Thu 2024-06-20 10:15:42 UTC; 2h 5min ago\nMain PID: 1234 (postgres)\n  Tasks: 8\n  Memory: 15.2M\n  CGroup: \u002Fsystem.slice\u002Fpostgresql.service\n        ├─1234 \u002Fusr\u002Flib\u002Fpostgresql\u002F19\u002Fbin\u002Fpostgres -D \u002Fvar\u002Flib\u002Fpostgresql\u002F19\u002Fmain\n        ├─1235 postgres: checkpointer process\n        ├─1236 postgres: writer process\n        └─\n","console",[99,458,459,464,469,474,479,484,490,496,502,508,514],{"__ignoreMap":203},[207,460,461],{"class":209,"line":210},[207,462,463],{"class":258},"● postgresql.service - PostgreSQL database server\n",[207,465,466],{"class":209,"line":248},[207,467,468],{"class":258},"  Loaded: loaded (\u002Flib\u002Fsystemd\u002Fsystem\u002Fpostgresql.service; enabled)\n",[207,470,471],{"class":209,"line":314},[207,472,473],{"class":258},"  Active: active (running) since Thu 2024-06-20 10:15:42 UTC; 2h 5min ago\n",[207,475,476],{"class":209,"line":335},[207,477,478],{"class":258},"Main PID: 1234 (postgres)\n",[207,480,481],{"class":209,"line":355},[207,482,483],{"class":258},"  Tasks: 8\n",[207,485,487],{"class":209,"line":486},6,[207,488,489],{"class":258},"  Memory: 15.2M\n",[207,491,493],{"class":209,"line":492},7,[207,494,495],{"class":258},"  CGroup: \u002Fsystem.slice\u002Fpostgresql.service\n",[207,497,499],{"class":209,"line":498},8,[207,500,501],{"class":258},"        ├─1234 \u002Fusr\u002Flib\u002Fpostgresql\u002F19\u002Fbin\u002Fpostgres -D \u002Fvar\u002Flib\u002Fpostgresql\u002F19\u002Fmain\n",[207,503,505],{"class":209,"line":504},9,[207,506,507],{"class":258},"        ├─1235 postgres: checkpointer process\n",[207,509,511],{"class":209,"line":510},10,[207,512,513],{"class":258},"        ├─1236 postgres: writer process\n",[207,515,517],{"class":209,"line":516},11,[207,518,519],{"class":258},"        └─\n",[34,521,522],{},"Or you can run this command:",[198,524,526],{"className":454,"code":525,"language":456,"meta":203,"style":203},"pg_isready\n",[99,527,528],{"__ignoreMap":203},[207,529,530],{"class":209,"line":210},[207,531,525],{"class":258},[34,533,534],{},"The command will output this message if the database is ready to accept\nconnections:",[198,536,538],{"className":454,"code":537,"language":456,"meta":203,"style":203},"\u002Fvar\u002Frun\u002Fpostgresql:5432 - accepting connections\n",[99,539,540],{"__ignoreMap":203},[207,541,542],{"class":209,"line":210},[207,543,537],{"class":258},[84,545,547],{"id":546},"_4-setting-the-default-superuser-password","4. Setting the Default Superuser Password",[34,549,550,551,554],{},"Out of the box, PostgreSQL creates a default superuser account named ",[99,552,553],{},"postgres","\nthat relies on peer authentication (meaning it authenticates based on your\nsystem user). To prepare our server for remote application connectivity and\nadministrative tasks, we set a secure password for this account.",[34,556,557,558,561],{},"Access the PostgreSQL interactive terminal (using the ",[99,559,560],{},"psql"," client):",[198,563,565],{"className":200,"code":564,"language":202,"meta":203,"style":203},"sudo --login --user postgres psql\n",[99,566,567],{"__ignoreMap":203},[207,568,569,571,574,577,580],{"class":209,"line":210},[207,570,238],{"class":237},[207,572,573],{"class":258}," --login",[207,575,576],{"class":258}," --user",[207,578,579],{"class":241}," postgres",[207,581,582],{"class":241}," psql\n",[34,584,585,586,589],{},"Run the following SQL commands to set a strorng password (replace\n",[99,587,588],{},"your_secure_password_here"," with a robust generated password):",[198,591,595],{"className":592,"code":593,"language":594,"meta":203,"style":203},"language-sql shiki shiki-themes github-dark","ALTER USER postgres PASSWORD 'your_secure_password_here';\n","sql",[99,596,597],{"__ignoreMap":203},[207,598,599,602,605,608,611,614],{"class":209,"line":210},[207,600,601],{"class":213},"ALTER",[207,603,604],{"class":213}," USER",[207,606,607],{"class":217}," postgres ",[207,609,610],{"class":213},"PASSWORD",[207,612,613],{"class":241}," 'your_secure_password_here'",[207,615,616],{"class":217},";\n",[34,618,619,620,622,623,626],{},"Exit the ",[99,621,560],{}," prompt by typing ",[99,624,625],{},"\\q",", and you will be returned to your regular\ninteractive shell.",[34,628,629],{},"With PostgreSQL installed and the primary superuser secured, you are ready for\nthe next step: configuring users, databases and establishing secure remote\nconnectivity.",[67,631,633],{"id":632},"initial-configuration-and-user-management","Initial Configuration and User Management",[34,635,636,637,639],{},"Now that PostgreSQL is up and running on your Azure VM, the next step is moving\naway from the default administrative setup. At Weburz, we follow the principle\nof least privilege-meaning we avoid using the master ",[99,638,553],{}," superuser for\nday-to-day application connections. Instead, we create dedicated databases and\nrestricted users tailored to each application.",[34,641,642],{},"Here is how we handle initial configuration and user management:",[84,644,646],{"id":645},"_1-accessing-the-postgresql-prompt","1. Accessing the PostgreSQL Prompt",[34,648,649,650,652],{},"To create databases and users, you first need to access the database management\ninterface. Switch back to the system ",[99,651,553],{}," user and launch the interactive\nterminal:",[198,654,655],{"className":200,"code":564,"language":202,"meta":203,"style":203},[99,656,657],{"__ignoreMap":203},[207,658,659,661,663,665,667],{"class":209,"line":210},[207,660,238],{"class":237},[207,662,573],{"class":258},[207,664,576],{"class":258},[207,666,579],{"class":241},[207,668,582],{"class":241},[84,670,672],{"id":671},"_2-creating-a-dedicated-database","2. Creating a Dedicated Database",[34,674,675,676,678,679,682],{},"Instead of cluttering the default ",[99,677,553],{}," database, create a dedicated\ndatabase for your project. Run the following SQL command (replace ",[99,680,681],{},"lorem"," with\nyour actual project name):",[198,684,686],{"className":592,"code":685,"language":594,"meta":203,"style":203},"CREATE DATABASE lorem;\n",[99,687,688],{"__ignoreMap":203},[207,689,690,693,696,699],{"class":209,"line":210},[207,691,692],{"class":213},"CREATE",[207,694,695],{"class":213}," DATABASE",[207,697,698],{"class":237}," lorem",[207,700,616],{"class":217},[84,702,704],{"id":703},"_3-creating-a-dedicated-user-and-assigning-privileges","3. Creating a Dedicated User and Assigning Privileges",[34,706,707],{},"Next, create a non-superuser account for your application. This limits potential\nsecurity risks if your application credentials are ever compromised.",[34,709,710],{},"Run the following commands to create a user and grant them full ownership and\nprivileges over your new database:",[198,712,714],{"className":592,"code":713,"language":594,"meta":203,"style":203},"CREATE USER john_doe WITH ENCRYPTED PASSWORD 'your_strong_app_password';\nGRANT ALL PRIVILEGES ON DATABASE lorem TO john_doe;\n",[99,715,716,739],{"__ignoreMap":203},[207,717,718,720,722,725,728,731,734,737],{"class":209,"line":210},[207,719,692],{"class":213},[207,721,604],{"class":213},[207,723,724],{"class":237}," john_doe",[207,726,727],{"class":213}," WITH",[207,729,730],{"class":213}," ENCRYPTED",[207,732,733],{"class":213}," PASSWORD",[207,735,736],{"class":241}," 'your_strong_app_password'",[207,738,616],{"class":217},[207,740,741,744,747,750,752,755,758],{"class":209,"line":248},[207,742,743],{"class":213},"GRANT",[207,745,746],{"class":217}," ALL PRIVILEGES ",[207,748,749],{"class":213},"ON",[207,751,695],{"class":213},[207,753,754],{"class":217}," lorem ",[207,756,757],{"class":213},"TO",[207,759,760],{"class":217}," john_doe;\n",[34,762,763,764,767],{},"PostgreSQL 15+ adds stricter permission structures so you should also grant\nprivileges on the default ",[99,765,766],{},"public"," schema within your database for the\napplication to create tables:",[198,769,771],{"className":592,"code":770,"language":594,"meta":203,"style":203},"\\c lorem\nGRANT ALL ON SCHEMA public TO lorem;\n",[99,772,773,778],{"__ignoreMap":203},[207,774,775],{"class":209,"line":210},[207,776,777],{"class":217},"\\c lorem\n",[207,779,780,782,785,787,790,793,795],{"class":209,"line":248},[207,781,743],{"class":213},[207,783,784],{"class":217}," ALL ",[207,786,749],{"class":213},[207,788,789],{"class":213}," SCHEMA",[207,791,792],{"class":217}," public ",[207,794,757],{"class":213},[207,796,797],{"class":217}," lorem;\n",[34,799,800,801,803,804,806],{},"Type ",[99,802,625],{}," to exit the ",[99,805,560],{}," shell, and exit to return to your normal user\naccount.",[84,808,810],{"id":809},"_4-tuning-basic-resource-configurations","4. Tuning Basic Resource Configurations",[34,812,813,814,817,818,821],{},"Before opening your database up to the network, it is a good idea to adjust a\nfew basic settings in the database configuration file, ",[99,815,816],{},"postgresql.conf",". By\ndefault, the main configuration file is typically located at\n",[99,819,820],{},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fpostgresql.conf"," (depending on your version of\nthe database and Operating System).",[34,823,824],{},"At Weburz, we do not recommend configuring this main file for two main reasons:",[826,827,828,831],"ol",{},[95,829,830],{},"It can potentially be overwritten by software updates.",[95,832,833],{},"In case of a catastrophic misconfiguration, we can always fallback to the\ndefault settings without worrying about data loss.",[34,835,836,837,840],{},"Instead our recommendations is to create a\n",[99,838,839],{},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d"," directory and add the user-defined\nconfigurations in there.",[198,842,844],{"className":454,"code":843,"language":456,"meta":203,"style":203},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F00shared.conf\n\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F01memory.conf\n\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F02server.conf\n",[99,845,846,851,856],{"__ignoreMap":203},[207,847,848],{"class":209,"line":210},[207,849,850],{"class":258},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F00shared.conf\n",[207,852,853],{"class":209,"line":248},[207,854,855],{"class":258},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F01memory.conf\n",[207,857,858],{"class":209,"line":314},[207,859,860],{"class":258},"\u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F02server.conf\n",[34,862,863,865],{},[150,864,406],{},": Make sure to provide legible and unambiguous file names to avoid\nconfusion and perhaps even version-control the configurations, if possible.",[34,867,868],{},"The default configurations the database server ships with is a good source of\nreference for customisation. You can view it and you will the majority of it is\ncommented out with additional reference documentation:",[198,870,872],{"className":454,"code":871,"language":456,"meta":203,"style":203},"sudo less \u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fpostgresql.conf\n",[99,873,874],{"__ignoreMap":203},[207,875,876],{"class":209,"line":210},[207,877,871],{"class":258},[34,879,880],{},"Look for the following core parameters to tweak for baseline performance:",[92,882,883,889],{},[95,884,885,888],{},[99,886,887],{},"max_connections",": Default is usually 100. If you have many microservices or\nserverless functions connecting, you might need to adjust this, but keep it\nbalanced to prevent exhausting your RAM.",[95,890,891,893],{},[99,892,101],{},": As a rule of thumb for dedicated database servers, set this\nto roughly 25% of your Azure VM's total RAM to optimize caching.",[34,895,896],{},"Save and close the file, then restart PostgreSQL to apply your configuration\nchanges:",[198,898,900],{"className":200,"code":899,"language":202,"meta":203,"style":203},"sudo systemctl restart postgresql\n",[99,901,902],{"__ignoreMap":203},[207,903,904,906,908,911],{"class":209,"line":210},[207,905,238],{"class":237},[207,907,438],{"class":241},[207,909,910],{"class":241}," restart",[207,912,444],{"class":241},[67,914,916],{"id":915},"enabling-remote-access-and-network-security","Enabling Remote Access and Network Security",[34,918,919,920,923],{},"By default, PostgreSQL is locked down to accept connections only from\n",[99,921,922],{},"localhost",". This is great for security out of the box, but at some point, your\nbackend application servers-living on different nodes or cloud environments-need\nto talk to the database.",[34,925,926],{},"At Weburz, we enable remote access without compromising our perimeter security\nby combining PostgreSQL's native configuration files, Azure Network Security\nGroup (NSG) rules and with our Tailscale zero-trust network. We obviously cannot\nshare the details of our security protocols but we plan on writing a reference\narticle on the topic some time in the near future.",[34,928,929],{},"Regardless, here is how you can configure a safe, encrypted remote access\nconfiguration for the database as a first line of defence:",[84,931,933,934,937],{"id":932},"_1-update-the-listen_addresses-setting","1. Update the ",[99,935,936],{},"listen_addresses"," setting",[34,939,940],{},"First, we need to tell PostgreSQL to listen for incoming connections beyond just\nthe local machine.",[34,942,943],{},"Open or create a new user-defined configuration file:",[198,945,947],{"className":454,"code":946,"language":456,"meta":203,"style":203},"sudo vim \u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fconf.d\u002F00-networking.conf\n",[99,948,949],{"__ignoreMap":203},[207,950,951],{"class":209,"line":210},[207,952,946],{"class":258},[34,954,955],{},"Add the following line to the file:",[198,957,961],{"className":958,"code":959,"language":960,"meta":203,"style":203},"language-conf shiki shiki-themes github-dark","listen_addresses = '*'\n","conf",[99,962,963],{"__ignoreMap":203},[207,964,965],{"class":209,"line":210},[207,966,959],{},[34,968,969,971,972,974,975,977,978,980,981,984],{},[150,970,152],{},": As a source of reference, find the line that controls\n",[99,973,936],{}," in the main configuration file\n(",[99,976,820],{},"), by default, it is commented\nout or set to ",[99,979,922],{},". Change it to listen on all interfaces (",[99,982,983],{},"*",") or\nspecifically on your server's internal Tailscale IP address:",[198,986,988],{"className":958,"code":987,"language":960,"meta":203,"style":203},"list_address = \"100.64.123.123\"\n",[99,989,990],{"__ignoreMap":203},[207,991,992],{"class":209,"line":210},[207,993,987],{},[34,995,996,998,999,1001],{},[150,997,152],{},": Setting this to ",[99,1000,983],{}," is safe only if there are strict firewall\nprotection and the users properly using the authentication tokens we will\ndiscuss next.",[84,1003,1005,1006],{"id":1004},"_2-configure-client-authentication-in-pg_hbaconf","2. Configure Client Authentication in ",[99,1007,1008],{},"pg_hba.conf",[34,1010,1011,1012,1014],{},"PostgreSQL uses the ",[99,1013,1008],{}," (Host-Based Authentication) file to control\nwhich clients are allowed to connect, to which databases, and using what\nauthentication methods.",[198,1016,1018],{"className":454,"code":1017,"language":456,"meta":203,"style":203},"sudo vim \u002Fetc\u002Fpostgresql\u002F\u003CVERSION>\u002Fmain\u002Fpg_hba.conf\n",[99,1019,1020],{"__ignoreMap":203},[207,1021,1022],{"class":209,"line":210},[207,1023,1017],{"class":258},[34,1025,1026,1027,1030],{},"Scroll to the bottom of the file where IPv4 and IPv6 connections are already\ndefined by default. Instead of allowing connections from anywhere, we want to\nexplicitly whitelist our application servers or our secure Tailscale IP range\n(Tailscale typically uses the ",[99,1028,1029],{},"100.64.0.0\u002F10"," CGNAT block).",[34,1032,1033],{},"Add a rule like this:",[198,1035,1040],{"className":1036,"code":1038,"language":1039,"meta":203},[1037],"language-text","# TYPE  DATABASE        USER            ADDRESS                 METHOD\nhost    lorem           john_doe        100.64.0.0\u002F10           scram-sha-256\n","text",[99,1041,1038],{"__ignoreMap":203},[92,1043,1044,1050,1055,1061,1066],{},[95,1045,1046,1049],{},[99,1047,1048],{},"host",": Specifies a network TCP\u002FIP connection.",[95,1051,1052,1054],{},[99,1053,681],{},": The specific database name.",[95,1056,1057,1060],{},[99,1058,1059],{},"john_doe",": The specific application user.",[95,1062,1063,1065],{},[99,1064,1029],{},": The Tailscale secure subnet (you can also specify exact\nindividual server IP addresses here for tighter security).",[95,1067,1068,1071],{},[99,1069,1070],{},"scram-sha-256",": The modern, secure password-hashing standard used by current\nPostgreSQL versions.",[34,1073,1074,1076,1077,1080,1081,1083,1084,1086],{},[150,1075,406],{},": At Weburz, we automate our database schema migration process in a CI\u002FCD\nenvironment. For that we also assign a specific user (e.g., ",[99,1078,1079],{},"migration_user",")\nand fine-tune it's permissions by following the principles of least-privileges.\nWe then configure the ",[99,1082,1008],{}," file to allow the ",[99,1085,1079],{}," to only\nmake network requests from the CI\u002FCD server it is running on.",[34,1088,1089],{},"When you are done configuring the authentication settings, save and close the\nfile.",[84,1091,1093],{"id":1092},"_3-enforce-azure-network-security-groups-nsgs","3. Enforce Azure Network Security Groups (NSGs)",[34,1095,1096,1097,415],{},"Because our database VM lives in Azure, defense-in-depth is critical. Even\nthough PostgreSQL is now configured to accept connections via Tailscale, we\nensure that Azure Network Security Groups (NSGs) block standard public inbound\ntraffic on port ",[99,1098,1099],{},"5432",[92,1101,1102,1115],{},[95,1103,1104,1105,1107,1108,1111,1112,415],{},"Ensure there are no inbound rules allowing port ",[99,1106,1099],{}," from ",[99,1109,1110],{},"Any"," or\n",[99,1113,1114],{},"Internet",[95,1116,1117],{},"If your application and database are both part of the same Azure Virtual\nNetwork (VNet) or connected via Tailscale, internal routing handles the\nhandshake safely without ever opening ports to the public internet.",[84,1119,1121],{"id":1120},"_4-apply-changes","4. Apply Changes",[34,1123,1124],{},"Restart PostgreSQL to load your new network and authentication rules:",[198,1126,1127],{"className":200,"code":899,"language":202,"meta":203,"style":203},[99,1128,1129],{"__ignoreMap":203},[207,1130,1131,1133,1135,1137],{"class":209,"line":210},[207,1132,238],{"class":237},[207,1134,438],{"class":241},[207,1136,910],{"class":241},[207,1138,444],{"class":241},[34,1140,1141],{},"With remote access securely established over your private mesh network, your\ndatabase is ready for production traffic. Next, let's look at how we harden\nsecurity further and manage routine maintenance.",[67,1143,1145],{"id":1144},"hardening-security-and-user-authentication","Hardening Security and User Authentication",[34,1147,1148],{},"Getting your remote connection up and running is a major milestone, but at\nWeburz, we treat network-level isolation as only the first line of defense. A\ntruly production-ready database requires deep-layer security hardening to\nprotect your data against unauthorized access, credential leaks, and\ninterception.",[34,1150,1151],{},"Here is how we lock down our self-hosted PostgreSQL instances:",[84,1153,1155],{"id":1154},"_1-enforcing-strong-password-policies-and-scram-sha-256","1. Enforcing Strong Password Policies and SCRAM-SHA-256",[34,1157,1158,1159,1162],{},"Older versions of PostgreSQL defaulted to the legacy ",[99,1160,1161],{},"md5"," password-hashing\nalgorithm, which is susceptible to brute-force attacks if intercepted. Modern\nversions default to SCRAM-SHA-256, a much stronger challenge-response\nauthentication mechanism.",[34,1164,1165,1166,1169,1170,1172],{},"To ensure all database users utilize this robust standard, verify your\n",[99,1167,1168],{},"password_encryption"," setting inside your ",[99,1171,816],{}," file:",[198,1174,1177],{"className":1175,"code":1176,"language":1039,"meta":203},[1037],"password_encryption  = scram-sha-256\n",[99,1178,1176],{"__ignoreMap":203},[34,1180,1181],{},"Whenever you create new database users or rotate existing passwords, PostgreSQL\nwill automatically hash them securely using SCRAM.",[84,1183,1185],{"id":1184},"_2-implementing-the-principle-of-least-privilege","2. Implementing the Principle of Least Privilege",[34,1187,1188,1189,415],{},"We touched on this during user setup, but it bears repeating as a core hardening\npractice: ",[150,1190,1191,1192,1194],{},"never let your application connect as the superuser (",[99,1193,553],{},")",[92,1196,1197,1203],{},[95,1198,1199,1200,1202],{},"Restrict your application user (",[99,1201,1059],{},") so it only has permissions on the\nspecific schemas and tables it needs.",[95,1204,1205],{},"Revoke public schema access from unprivileged users if they don't need to\ncreate objects there:",[198,1207,1209],{"className":592,"code":1208,"language":594,"meta":203,"style":203},"REVOKE CREATE ON SCHEMA public FROM PUBLIC;\n",[99,1210,1211],{"__ignoreMap":203},[207,1212,1213,1216,1219,1222,1224,1226,1229],{"class":209,"line":210},[207,1214,1215],{"class":213},"REVOKE",[207,1217,1218],{"class":213}," CREATE",[207,1220,1221],{"class":213}," ON",[207,1223,789],{"class":213},[207,1225,792],{"class":217},[207,1227,1228],{"class":213},"FROM",[207,1230,1231],{"class":217}," PUBLIC;\n",[92,1233,1234],{},[95,1235,1236,1237,1239,1240,1243],{},"Regularly audit your user roles and permissions using ",[99,1238,560],{}," commands like\n",[99,1241,1242],{},"\\du"," to ensure no lingering test accounts or overly permissive roles exist in\nproduction.",[84,1245,1247],{"id":1246},"_3-encrypting-data-in-transit-with-ssltls","3. Encrypting Data in Transit with SSL\u002FTLS",[34,1249,1250],{},"Even though our traffic travels securely over our Tailscale mesh network,\ndefense-in-depth dictates that all data moving between your application and the\ndatabase should be encrypted.",[34,1252,1253],{},"PostgreSQL supports native SSL\u002FTLS connections out of the box. To enforce\nencrypted connections:",[826,1255,1256,1267,1276,1332,1351],{},[95,1257,1258,1259],{},"Open the configuration file:",[198,1260,1261],{"className":454,"code":946,"language":456,"meta":203,"style":203},[99,1262,1263],{"__ignoreMap":203},[207,1264,1265],{"class":209,"line":210},[207,1266,946],{"class":258},[95,1268,1269,1270],{},"Add the following line to enable the SSL parameter:",[198,1271,1274],{"className":1272,"code":1273,"language":1039,"meta":203},[1037],"ssl = on\n",[99,1275,1273],{"__ignoreMap":203},[95,1277,1278,1279,1285,1288,1289,1329,1331],{},"Point PostgreSQL to your SSL certificate and private key files (you can\ngenerate self-signed certificates for internal use or provision them via\nLet's Encrypt\u002Finternal CA):",[198,1280,1283],{"className":1281,"code":1282,"language":1039,"meta":203},[1037],"ssl_cert_file = '\u002Fetc\u002Fssl\u002Fcerts\u002Fssl-cert-snakeoil.pem'\nssl_key_file = '\u002Fetc\u002Fssl\u002Fprivate\u002Fssl-cert-snakeoil.key'\n",[99,1284,1282],{"__ignoreMap":203},[1286,1287],"br",{},"To generate your own SSL certificate for the PostgreSQL server and client,\nyou can run the following commands:",[198,1290,1292],{"className":200,"code":1291,"language":202,"meta":203,"style":203},"openssl req -new -x509 -days 365 -nodes -out server.crt -keyout server.key\n",[99,1293,1294],{"__ignoreMap":203},[207,1295,1296,1299,1302,1305,1308,1311,1314,1317,1320,1323,1326],{"class":209,"line":210},[207,1297,1298],{"class":237},"openssl",[207,1300,1301],{"class":241}," req",[207,1303,1304],{"class":258}," -new",[207,1306,1307],{"class":258}," -x509",[207,1309,1310],{"class":258}," -days",[207,1312,1313],{"class":258}," 365",[207,1315,1316],{"class":258}," -nodes",[207,1318,1319],{"class":258}," -out",[207,1321,1322],{"class":241}," server.crt",[207,1324,1325],{"class":258}," -keyout",[207,1327,1328],{"class":241}," server.key\n",[1286,1330],{},"You will be prompted with a few questions about the certificate and after\nanswering them you will receive your self-signed certificates.",[95,1333,1334,1335,1337,1338,1340,1341,1344,1345],{},"To strictly force all clients to use encrypted connections, update your\n",[99,1336,1008],{}," file, replacing ",[99,1339,1048],{}," with ",[99,1342,1343],{},"hostssl"," for your connection\nrules:",[198,1346,1349],{"className":1347,"code":1348,"language":1039,"meta":203},[1037],"# TYPE    DATABASE        USER            ADDRESS                 METHOD\nhostssl   lorem           john_doe        100.64.0.0\u002F10           scram-sha-256\n",[99,1350,1348],{"__ignoreMap":203},[95,1352,1353,1354],{},"Save your changes and restart PostgreSQL one final time to enforce SSL:",[198,1355,1356],{"className":200,"code":899,"language":202,"meta":203,"style":203},[99,1357,1358],{"__ignoreMap":203},[207,1359,1360,1362,1364,1366],{"class":209,"line":210},[207,1361,238],{"class":237},[207,1363,438],{"class":241},[207,1365,910],{"class":241},[207,1367,444],{"class":241},[34,1369,1370],{},"With your database fully hardened against threats, secure authentication\nenforced, and encrypted channels established, your server is safe and ready. In\nour final section, we will look at how we automate backups and monitor\nperformance to keep things running smoothly.",[67,1372,1374],{"id":1373},"setting-up-automated-backups-and-monitoring","Setting Up Automated Backups and Monitoring",[34,1376,1377,1378,1381,1382,415],{},"Even the most secure and well-optimized database server is vulnerable to the\nunexpected-whether it is human error (like an accidental ",[99,1379,1380],{},"DROP TABLE"," in\nproduction), hardware failure, or silent data corruption. At Weburz, we operate\nunder a simple rule: ",[150,1383,1384],{},"if it isn't backed up automatically, it doesn't exist",[34,1386,1387],{},"Here is how we set up a robust backup and monitoring routine for our self-hosted\nPostgreSQL instances:",[84,1389,1391,1392],{"id":1390},"_1-automated-logical-backups-with-pg_dump","1. Automated Logical Backups with ",[99,1393,1394],{},"pg_dump",[34,1396,1397,1398,1400,1401,1403],{},"For routine, lightweight backups, PostgreSQL provides the built-in utility\n",[99,1399,1394],{},". We use a simple shell script combined with ",[99,1402,425],{}," timers to export\nour databases daily.",[34,1405,1406],{},"A basic backup command looks like this:",[198,1408,1410],{"className":200,"code":1409,"language":202,"meta":203,"style":203},"pg_dump --username=postgres --dbname=lorem \\\n  --format=custom --large-objects --verbose \\\n  --file=\u002Fvar\u002Fbackups\u002Fpostgresql\u002Florem_prod_$(date +%F).dump\n",[99,1411,1412,1424,1437],{"__ignoreMap":203},[207,1413,1414,1416,1419,1422],{"class":209,"line":210},[207,1415,1394],{"class":237},[207,1417,1418],{"class":258}," --username=postgres",[207,1420,1421],{"class":258}," --dbname=lorem",[207,1423,311],{"class":258},[207,1425,1426,1429,1432,1435],{"class":209,"line":248},[207,1427,1428],{"class":258},"  --format=custom",[207,1430,1431],{"class":258}," --large-objects",[207,1433,1434],{"class":258}," --verbose",[207,1436,311],{"class":258},[207,1438,1439,1442,1445,1448],{"class":209,"line":314},[207,1440,1441],{"class":258},"  --file=\u002Fvar\u002Fbackups\u002Fpostgresql\u002Florem_prod_$(",[207,1443,1444],{"class":237},"date",[207,1446,1447],{"class":241}," +%F",[207,1449,1450],{"class":258},").dump\n",[92,1452,1453,1463],{},[95,1454,1455,1456,1459,1460,415],{},"The ",[99,1457,1458],{},"--format=custom"," option produces a custom archive format, which is\ncompressed and allows flexible restoration using ",[99,1461,1462],{},"pg_restore",[95,1464,1455,1465,1468],{},[99,1466,1467],{},"--large-objects"," option includes large objects in the dump.",[84,1470,1472],{"id":1471},"_2-secure-offsite-storage-with-restic","2. Secure Offsite Storage with Restic",[34,1474,1475],{},"Local backups stored on the same Azure VM won't save you if the entire disk or\nregion suffers a catastrophic failure. To protect against this, we push our\nencrypted database dumps to secure offsite cloud storage.",[34,1477,1478,1479,1484],{},"While a deep dive into our disaster recovery pipeline is coming in a future\ndedicated blog post, we rely heavily on ",[38,1480,1483],{"href":1481,"rel":1482},"https:\u002F\u002Frestic.net",[42],"Restic",". It is a\nfast, secure, and incredibly efficient backup program-to handle deduplicated,\nencrypted offsite snapshots of our backup directories. It keeps our historical\nbackups safe without ballooning our storage costs.",[84,1486,1488],{"id":1487},"_3-monitoring-database-performance-and-health","3. Monitoring Database Performance and Health",[34,1490,1491],{},"You cannot manage what you do not measure. To keep an eye on CPU usage, memory\npressure, disk I\u002FO, and active connections on our Azure VM, we implement\nlightweight monitoring tools:",[92,1493,1494,1514,1522],{},[95,1495,1496,1501,1502,1507,1508,1513],{},[38,1497,1500],{"href":1498,"rel":1499},"https:\u002F\u002Fgithub.com\u002Fprometheus\u002Fnode_exporter",[42],"Node Exporter"," &\n",[38,1503,1506],{"href":1504,"rel":1505},"https:\u002F\u002Fprometheus.io",[42],"Prometheus"," \u002F ",[38,1509,1512],{"href":1510,"rel":1511},"https:\u002F\u002Fgrafana.com",[42],"Grafana",": To track\nsystem-level metrics and visualize trends over time.",[95,1515,1516,1521],{},[38,1517,1520],{"href":1518,"rel":1519},"https:\u002F\u002Fazure.microsoft.com\u002Fen-us\u002Fproducts\u002Fmonitor",[42],"Azure Monitor"," to provide\nus with general resource consumption feedback and cost alerts.",[95,1523,1524,1525,1527,1528],{},"PostgreSQL Activity Queries: For quick health checks, you can always jump into\n",[99,1526,560],{}," and check active queries to spot performance bottlenecks or locked\ntables:",[198,1529,1531],{"className":592,"code":1530,"language":594,"meta":203,"style":203},"SELECT pid, usename, query, state, age(clock_timestamp(), query_start) AS duration\nFROM pg_stat_activity\nWHERE state != 'idle';\n",[99,1532,1533,1553,1560],{"__ignoreMap":203},[207,1534,1535,1538,1541,1544,1547,1550],{"class":209,"line":210},[207,1536,1537],{"class":213},"SELECT",[207,1539,1540],{"class":217}," pid, usename, query, ",[207,1542,1543],{"class":213},"state",[207,1545,1546],{"class":217},", age(clock_timestamp(), query_start) ",[207,1548,1549],{"class":213},"AS",[207,1551,1552],{"class":217}," duration\n",[207,1554,1555,1557],{"class":209,"line":248},[207,1556,1228],{"class":213},[207,1558,1559],{"class":217}," pg_stat_activity\n",[207,1561,1562,1565,1568,1571,1574],{"class":209,"line":314},[207,1563,1564],{"class":213},"WHERE",[207,1566,1567],{"class":213}," state",[207,1569,1570],{"class":213}," !=",[207,1572,1573],{"class":241}," 'idle'",[207,1575,616],{"class":217},[67,1577,1579],{"id":1578},"conclusion","Conclusion",[34,1581,1582],{},"Self-hosting your own PostgreSQL database server doesn't have to be a leap into\nthe unknown. By combining the right Azure infrastructure, a secure zero-trust\nnetwork like Tailscale, and automated maintenance workflows, you can cut down on\nexpensive cloud bills while retaining absolute control over your company's data.",[34,1584,1585],{},"At Weburz, making this switch has given us both peace of mind and financial\nfreedom-and with this guide, you have the exact blueprint to do it yourself!",[1587,1588,1589],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":203,"searchDepth":248,"depth":248,"links":1591},[1592,1598,1604,1610,1618,1623,1629],{"id":69,"depth":248,"text":70,"children":1593},[1594,1595,1596,1597],{"id":86,"depth":314,"text":87},{"id":108,"depth":314,"text":109},{"id":123,"depth":314,"text":124},{"id":156,"depth":314,"text":157},{"id":177,"depth":248,"text":178,"children":1599},[1600,1601,1602,1603],{"id":190,"depth":314,"text":191},{"id":368,"depth":314,"text":369},{"id":418,"depth":314,"text":419},{"id":546,"depth":314,"text":547},{"id":632,"depth":248,"text":633,"children":1605},[1606,1607,1608,1609],{"id":645,"depth":314,"text":646},{"id":671,"depth":314,"text":672},{"id":703,"depth":314,"text":704},{"id":809,"depth":314,"text":810},{"id":915,"depth":248,"text":916,"children":1611},[1612,1614,1616,1617],{"id":932,"depth":314,"text":1613},"1. Update the listen_addresses setting",{"id":1004,"depth":314,"text":1615},"2. Configure Client Authentication in pg_hba.conf",{"id":1092,"depth":314,"text":1093},{"id":1120,"depth":314,"text":1121},{"id":1144,"depth":248,"text":1145,"children":1619},[1620,1621,1622],{"id":1154,"depth":314,"text":1155},{"id":1184,"depth":314,"text":1185},{"id":1246,"depth":314,"text":1247},{"id":1373,"depth":248,"text":1374,"children":1624},[1625,1627,1628],{"id":1390,"depth":314,"text":1626},"1. Automated Logical Backups with pg_dump",{"id":1471,"depth":314,"text":1472},{"id":1487,"depth":314,"text":1488},{"id":1578,"depth":248,"text":1579},"Infrastructure","\u002Fblog\u002Fself-hosting-postgresql.webp","2026-08-10","Take full control of your data by setting up your own self-hosted PostgreSQL database. Discover how to configure, secure, and manage production-ready instances efficiently.","md",{},true,"\u002Fblog\u002Fsecurely-self-hosting-postgresql",{"title":28,"description":1633},"blog\u002Fsecurely-self-hosting-postgresql","yleRBlILTi7BfsnvF-SQYP-d9zTgL9O-jGholiVpewY",[1642,7],{"title":1643,"path":1644,"stem":1645,"description":1646,"date":1647,"children":-1},"Hello, world","\u002Fblog\u002Fhello-world","blog\u002Fhello-world","Kicking off the Weburz engineering blog — what to expect, and why we're publishing in the open.","2026-05-22",1786535210553]