SSD Nodes Learn
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-07-24

Ubuntu पर Self-signed Certificate कैसे बनाएँ

Ubuntu 24.04 पर Chrome द्वारा स्वीकार किए जाने वाले TLS certificate बनाने का सही तरीका सीखें। इसमें SAN के साथ OpenSSL और Nginx/Apache का सही सेटअप शामिल है।

आप क्या बना रहे हैं

एक ऐसा self-signed TLS certificate जिसे modern browsers और clients स्वीकार कर सकें — सही subjectAltName, उचित key permissions, और nginx या Apache के साथ integrated. इसके साथ ही वह हिस्सा जो लगभग हर guide में छूट जाता है: अपने clients को इसे trust करना सिखाना, ताकि उन्हें warnings को bypass न करना पड़े और scripts में हमेशा के लिए curl -k को hard-code न करना पड़े. अंत में, एक five-command private CA, जो तब काम आएगा जब आपके पास एक के बजाय छह internal services हो जाएं.

सबसे पहले, निर्णय लें, क्योंकि self-signed certificate का उपयोग अक्सर गलत तरीके से किया जाता है. यदि service को public internet पर एक real DNS name के माध्यम से एक्सेस किया जा सकता है, तो इसे पढ़ना बंद करें और nginx पर certbot के साथ एक free Let's Encrypt certificate या Apache के equivalent का उपयोग करें. यह मुफ्त है, अपने आप renew हो जाता है, और दुनिया का हर browser इसे पहले से ही trust करता है. public site पर self-signed certificate का उपयोग करने से आपके users को security warnings को ignore करने की आदत पड़ जाती है, जो plain HTTP से भी बुरा है.

Self-signed तब सही tool है जब public internet का उपयोग न हो: जैसे आपके VPS पर WireGuard tunnel address से जुड़ा admin panel, private network पर staging box, backends के बीच service-to-service traffic, home-lab appliance, या वह placeholder certificate जिसे Webmin port 10000 पर अपने लिए generate करता है. Let's Encrypt वैसे भी 10.8.0.1 या git.internal.lan के लिए certificate जारी नहीं कर सकता — कोई भी public CA certificate में private IP या बनावटी TLD नहीं डालेगा. उन names के लिए, आप ही CA हैं.

नीचे दी गई सभी प्रक्रियाएँ एक fresh Ubuntu 24.04 box पर चलती हैं, जिसमें OpenSSL 3.0.x (confirm करने के लिए openssl version) है. यहाँ किसी भी चीज़ के लिए internet access की आवश्यकता नहीं है; यह सब air-gapped काम करता है.

Purane one-liner se Chrome dwara reject kiye gaye certs kyon bante hain

2017 se pehle ke sabhi tutorials mein diya gaya command aisa dikhta hai:

# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key -out selfsigned.crt

Yeh command kai interactive sawal puchta hai, Common Name field mein aapka hostname dalta hai, aur bina subjectAltName extension wala certificate banata hai. Woh certificate kaam nahi karega. Chrome ne version 58 mein Common Name padhna band kar diya tha, jo April 2017 mein aaya tha — RFC 2818 ne saal 2000 mein hi CN matching ko deprecated kar diya tha — aur Firefox, Safari, curl, aur Python bhi isi tarah kaam karte hain. Ek certificate SAN extension ke madhyam se server ki pehchan karta hai, ya phir bilkul nahi; browser aapko yeh error dikhata hai:

NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.

Trust-store mein badlav karne se yeh error theek nahi hoga, kyunki certificate mein koi sahi naam nahi hai. Agar aap abhi NET::ERR_CERT_COMMON_NAME_INVALID dekh rahe hain, to iska matlab hai ki aapke certificate mein SAN nahi hai (ya galat hai) aur aapko naya certificate banana hoga. Khushkismati se, iska samadhan sirf ek command hai.

ब्राउज़र द्वारा स्वीकार किए जाने वाले सर्टिफिकेट बनाएँ: एक कमांड

OpenSSL ने 1.1.1 में -addext flag जोड़ा है, जिसका अर्थ है कि अब आपको SAN इंजेक्ट करने के लिए पुराने गाइड्स की तरह config-file gymnastics की आवश्यकता नहीं है। Ubuntu 24.04 पर:

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
  -keyout /etc/ssl/private/git.internal.key \
  -out /etc/ssl/certs/git.internal.crt \
  -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"

प्रत्येक flag का कार्य:

  • -x509 signing request के बजाय सीधे self-signed certificate जारी करता है।
  • -newkey rsa:4096 उसी स्टेप में एक नई key जनरेट करता है। RSA 4096 पुराने clients के लिए सही है; यदि सभी कनेक्टिंग clients आधुनिक हैं, तो -newkey ec -pkeyopt ec_paramgen_curve:P-256 छोटा और तेज़ है।
  • -noenc पुराने -nodes का OpenSSL 3.x स्पेलिंग है: key पर कोई passphrase नहीं। दोनों स्पेलिंग काम करती हैं। passphrase वाली key का मतलब है कि nginx हर boot पर इनपुट के लिए रुक जाएगा, इसलिए server key के लिए आपको इसकी आवश्यकता नहीं है।
  • -days 730 — दो वर्ष; इस नंबर के बारे में expiry सेक्शन में अधिक जानकारी दी गई है।
  • -subj इंटरैक्टिव सवालों के जवाब inline देता है। CN अब केवल cosmetic है, लेकिन फिर भी इसे प्राथमिक नाम पर सेट करें; कुछ tools इसे प्रदर्शित करते हैं।
  • -addext "subjectAltName=..." सबसे महत्वपूर्ण flag है। वे सभी नाम और सभी IP लिखें जिन्हें clients टाइप करेंगे: hostnames के लिए DNS: entries (DNS:*.internal.lan जैसे wildcards मान्य हैं), और addresses के लिए IP: entries। यदि कोई https://10.8.0.1 पर ब्राउज़ करेगा, तो IP:10.8.0.1 entry वहां होनी चाहिए — केवल DNS-only SAN होने पर उन्हें NET::ERR_CERT_COMMON_NAME_INVALID फिर से प्राप्त होगा।

कनेक्शन सेटअप करने से पहले पुष्टि करें कि SAN सही ढंग से लागू हुआ है:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

सही output:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

यदि इसके बजाय No extensions in certificate प्रिंट होता है, तो सर्टिफिकेट में SAN नहीं है और ब्राउज़र इसे अस्वीकार कर देंगे — आगे बढ़ने के बजाय इसे फिर से जनरेट करें।

Key को सुरक्षित करें

यदि system के सभी users आपकी private key को पढ़ सकते हैं, तो वह private key नहीं है। Ubuntu पर, /etc/ssl/private पहले से ही 710 root:ssl-cert होता है, जो अनधिकृत पहुँच को रोकता है। फिर भी, file के permissions को स्पष्ट रूप से सेट करें:

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

nginx और Apache दोनों privileges कम करने से पहले certificates को root के रूप में पढ़ते हैं, इसलिए उनके लिए root:root mode 600 सही है। यदि key किसी ऐसे service के लिए है जो अपने स्वयं के user के रूप में चलती है और key को स्वयं load करती है — जैसे कि Node app, Gitea, या Python daemon — तो इसे उस service user के लिए chown करें, लेकिन mode 600 ही रखें। आपको कभी भी ये काम नहीं करने चाहिए: mode 644 का उपयोग करना, git repository में copy रखना, या /tmp में copy रखना।

इसे nginx में कॉन्फ़िगर करें

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name git.internal.lan;

    ssl_certificate     /etc/ssl/certs/git.internal.crt;
    ssl_certificate_key /etc/ssl/private/git.internal.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
sudo nginx -t && sudo systemctl reload nginx

nginx -t को reload होने से पहले syntax is ok और test is successful प्रिंट करना चाहिए। यदि यह SSL_CTX_use_PrivateKey_file() failed ... key values mismatch प्रिंट करता है, तो इसका अर्थ है कि certificate और key दो अलग-अलग generation runs से हैं — failure-modes सेक्शन देखें।

Apache में इसे कॉन्फ़िगर करें

sudo a2enmod ssl proxy proxy_http

यहाँ केवल ssl पर्याप्त नहीं है: नीचे दिया गया vhost ProxyPass का उपयोग करता है, और mod_proxy तथा mod_proxy_http के बिना config test Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration के साथ विफल हो जाएगा। vhost को /etc/apache2/sites-available/git-internal.conf के रूप में सेव करें:

<VirtualHost *:443>
    ServerName git.internal.lan
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/git.internal.crt
    SSLCertificateKeyFile   /etc/ssl/private/git.internal.key

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2

configtest को Syntax OK का उत्तर देना चाहिए। अब client machine से टेस्ट करें:

curl -v https://git.internal.lan/

और आपको एक error प्राप्त होगा:

curl: (60) SSL certificate problem: self-signed certificate

यह कोई bug नहीं है। यह TLS का काम करने का तरीका है: curl आपके certificate को नहीं पहचानता है और उस server से बात करने से मना कर देता है जिसे वह authenticate नहीं कर सकता। अगला section वास्तविक समाधान है — और यह वह तरीका नहीं है जो इंटरनेट पर आधे लोग इस समय कर रहे हैं।

Make clients trust it — and the anti-patterns to refuse

The wrong fixes first, named as what they are. curl -k (or --insecure) baked into a script, verify=False in Python requests, NODE_TLS_REJECT_UNAUTHORIZED=0 in Node — none of these make your certificate trusted. They switch certificate verification off, which means the client will happily talk to any server presenting any certificate, including one an attacker put in the path. You keep the overhead of TLS and lose the authentication that was its point. Worse, these flags metastasize: pasted into one cron job, then a deploy script, then production code, until nobody remembers which connections were supposed to be temporary. If a verify=False survives past the debugging session that spawned it, the design is wrong.

The right fix is to teach each client OS that this certificate is a trusted root. On Ubuntu and Debian clients:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

The line that matters in the output (a Running hooks in /etc/ca-certificates/update.d... block follows it):

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

Two gotchas hide in those lines. The file must end in .crt — a .pem extension is silently ignored and you get 0 added with no error message. And the contents must be PEM — the file starts with -----BEGIN CERTIFICATE-----; convert a DER binary first with openssl x509 -inform der -in file.der -out file.crt. Adding the self-signed certificate itself as a root works because a self-signed certificate is its own root.

After that, curl, wget, git, apt, and anything else that uses OpenSSL against the system bundle trusts the server with no flags at all. A handful of clients carry their own trust stores and need individual treatment:

  • Chrome/Chromium on Linux reads an NSS database, not the system store: sudo apt install libnss3-tools, then certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt per user.
  • Firefox has its own store: Settings → Privacy & Security → Certificates → Import, or flip security.enterprise_roots.enabled to true in about:config so it reads the system store.
  • Python requests ships its own CA bundle (certifi) and ignores the system store: pass verify="/usr/local/share/ca-certificates/git.internal.crt" or export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt.
  • Node.js: export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt.

On Windows clients, double-click the .crt and install it to Trusted Root Certification Authorities; on macOS, add it to the System keychain in Keychain Access and mark it Always Trust.

कई services के लिए एक root: एक छोटा private CA

Per-certificate trust के साथ scaling करना तुरंत कठिन हो जाता है: छह services और चार client machines का मतलब है चौबीस trust installs, और हर नई service इसे और बढ़ा देती है। इसका समाधान एक private CA है — clients केवल एक root पर भरोसा करते हैं, और आप प्रत्येक service के certificate को उसी से sign करते हैं।

एक आसान विकल्प mkcert है, जो Ubuntu 24.04 repos में उपलब्ध है। यह उन NSS stores (Chrome, Firefox) को भी handle करता है जिन्हें update-ca-certificates miss कर देता है:

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

mkcert -install एक root बनाता है और उसे उस machine के प्रत्येक trust store में register करता है; तीसरा command git.internal.lan+2.pem और git.internal.lan+2-key.pem generate करता है, जिन्हें ऊपर दिए गए nginx या Apache snippets में सीधे उपयोग किया जा सकता है। इसे development machine के लिए design किया गया है — root key उसी box पर रहती है जहाँ -install चलाया गया था — इसलिए यह dev laptop के लिए perfect है, लेकिन server fleet के लिए सही नहीं है।

Servers के लिए, plain OpenSSL पांच commands में पूरा CA बना देता है:

openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
  -out lab-ca.crt -subj "/CN=Lab Internal CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
  -CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crt

अंतिम command में एक समस्या है: openssl x509 -req default रूप से CSR से सभी extensions हटा देता है, जिसमें वह SAN भी शामिल है जिसे आपने सावधानी से जोड़ा था। -copy_extensions copy (एक OpenSSL 3.x option, इसलिए यह 24.04 पर काम करता है) उन्हें सुरक्षित रखता है; यदि आप इसे छोड़ देते हैं, तो signed certificate में कोई SAN नहीं होगा, और Chrome आपको फिर से NET::ERR_CERT_COMMON_NAME_INVALID दिखाएगा। पहले की तरह ही openssl x509 -noout -ext subjectAltName check से verify करें।

ऊपर दिए गए trust-store steps के माध्यम से clients को lab-ca.crt distribute करें — प्रत्येक machine पर केवल एक बार। lab-ca.key की सुरक्षा बहुत सावधानी से करें: mode 600 रखें, और आदर्श रूप से इसे ऐसे box पर रखें जो उन servers में से न हो जिन्हें यह sign करता है, क्योंकि जिसके पास यह key होगी, वह किसी भी name के लिए certificate बना सकता है जिसे आपके clients स्वीकार कर लेंगे।

Expiry और rotation

Public CA certificate की वैधता (lifetimes) कम हो रही है — CA/Browser Forum ने March 2026 में नए जारी किए गए publicly trusted certificates की सीमा 398 दिनों से घटाकर 200 दिन कर दी है। यह सीमा 2027 में 100 दिन और March 2029 तक 47 दिन हो जाएगी — लेकिन ये नियम केवल publicly trusted CAs पर लागू होते हैं। आपका private CA इनके अंतर्गत नहीं आता है, और browsers मैन्युअल रूप से इंस्टॉल किए गए roots के लिए इन्हें लागू नहीं करते हैं। एक वास्तविक प्रतिबंध लागू होता है: Apple platforms किसी भी ऐसे TLS server certificate को reject कर देते हैं जिसकी वैधता 825 दिनों से अधिक हो, चाहे उसे किसी ने भी जारी किया हो। इसलिए, यदि iPhones या Macs को कनेक्ट करना है, तो leaf certificates को दो साल या उससे कम रखें। -days 730 हर जगह इस मानक को पूरा करता है; दो साल के leaves के साथ दस साल का root एक सुरक्षित internal configuration है।

Long-lived certificates केवल एक ही तरह से विफल होते हैं: वे चुपचाप, एक साथ, उस तारीख पर विफल हो जाते हैं जिसे किसी को याद नहीं रहता। अपनी स्थिति की जाँच करें:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

Renewal को किसी वास्तविक calendar में दर्ज करें, या 30 दिन पहले cron का उपयोग करें — जब expiry इतने सेकंड के भीतर होती है, तो openssl x509 -checkend 2592000 -in cert.crt non-zero exit code देता है। यदि आप पहले से ही Uptime Kuma for status monitoring चला रहे हैं, तो इसके HTTPS monitors certificate expiry की चेतावनी मुफ्त में देते हैं।

Private CA के साथ rotation काफी सरल है: CSR-and-sign commands को फिर से चलाएँ, files को बदलें, और web server को reload करें। Root नहीं बदला है, इसलिए client को कुछ भी महसूस नहीं होगा।

Failure modes, with the strings you will see

NET::ERR_CERT_AUTHORITY_INVALID — यह certificate में कोई defect नहीं है, बल्कि trust install करने से पहले की expected state है। यदि root install करने के बाद भी यह बना रहता है: तो Linux पर Chrome, system store के बजाय NSS पढ़ता है (certutil step देखें); या copied file के अंत में .crt नहीं था और update-ca-certificates ने 0 added दिखाया; या server उस certificate के अलावा कोई दूसरा certificate पेश कर रहा है जिसे आपने trust किया था — openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256 के साथ fingerprints की तुलना करें।

NET::ERR_CERT_COMMON_NAME_INVALID — certificate में SAN नहीं है, या SAN में address bar वाले name का उल्लेख नहीं है। सामान्य मामला: SAN में DNS:git.internal.lan listed है लेकिन user ने https://10.8.0.1 browse किया है। Trust-store में बदलाव से इसे ठीक नहीं किया जा सकता; missing entry के साथ certificate को reissue करें।

curl: (60) SSL certificate problem: self-signed certificate — curl certificate पर trust नहीं करता है। self-signed certificate in certificate chain variant का मतलब भी वही है जो आपके private CA द्वारा signed certificate के लिए होता है। One-off fix: curl --cacert lab-ca.crt https://...; permanent fix: trust store। -k नहीं।

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (या Expecting: CERTIFICATE REQUEST, या no start line) — PEM confusion. आपने OpenSSL को गलत प्रकार की file दी है: certificate के स्थान पर key या CSR, या PEM के स्थान पर DER binary। head -1 filename आपको बताता है कि आपके पास वास्तव में क्या है — certificate -----BEGIN CERTIFICATE----- से शुरू होता है। DER के लिए, openssl x509 -inform der -in file.der -out file.crt से convert करें।

nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch) — certificate और key एक साथ संबंधित नहीं हैं, आमतौर पर इसलिए क्योंकि generation command दो बार चलाई गई थी और files mix हो गईं। openssl x509 -in git.internal.crt -noout -pubkey | sha256sum बनाम openssl pkey -in git.internal.key -pubout | sha256sum से confirm करें; matching hashes का मतलब है कि वे एक matching pair हैं। यदि वे अलग हैं, तो दोनों को एक साथ regenerate करें।

FAQ

self-signed certificate बनाने के बाद भी Chrome "Not secure" क्यों दिखाता है?

यदि error NET::ERR_CERT_AUTHORITY_INVALID है, तो certificate सही है — Chrome अभी उस पर भरोसा नहीं कर पा रहा है। इसे client के trust store में install करें (या अपने private CA root को)। याद रखें कि Linux पर, Chrome system store के बजाय certutil के माध्यम से NSS database का उपयोग करता है। यदि error NET::ERR_CERT_COMMON_NAME_INVALID है, तो certificate में URL से मेल खाने वाला Subject Alternative Name नहीं है; इसे -addext "subjectAltName=..." के साथ दोबारा जारी (reissue) करना होगा।

बिना -k के curl को self-signed certificate पर भरोसा कैसे दिलाएं?

Certificate (PEM format, .crt extension) को /usr/local/share/ca-certificates/ में copy करें और sudo update-ca-certificates चलाएं — output में 1 added लिखा होना चाहिए। इसके बाद curl किसी भी public certificate की तरह इसे verify करेगा। System में बदलाव किए बिना एक बार के request के लिए, curl --cacert /path/to/cert.crt केवल उसी file का उपयोग करके verify करता है; -k verification को पूरी तरह disable कर देता है और इसका उपयोग किसी के भी scripts में नहीं होना चाहिए।

self-signed certificate कितने समय तक valid रह सकता है?

तकनीकी रूप से आप इसे जितना चाहें उतना समय दे सकते हैं — CA/Browser Forum के नियम (अब 200 दिन, 2029 तक 47 दिन) केवल publicly trusted CAs पर लागू होते हैं, private trust पर नहीं। व्यवहार में, server certificates को 825 days तक ही सीमित रखें, क्योंकि Apple devices issuer की परवाह किए बिना इससे अधिक समय वाले certificates को reject कर देते हैं। दो साल (-days 730) के leaf certificates के साथ दस साल का private root एक सही default है; बस renewal के लिए calendar सेट कर लें, क्योंकि एक expired internal cert किसी ऐसी तारीख पर सब कुछ चुपचाप बंद कर देता है जो किसी को याद नहीं रहती।

क्या मुझे self-signed certificate का उपयोग करना चाहिए या Let's Encrypt का?

यदि service का public DNS name है और वह internet से reachable है, तो हमेशा Let's Encrypt का उपयोग करें — यह free है, automated है, और हर client द्वारा पहले से ही trusted है। Self-signed (या private CA) का उपयोग वहां करें जहाँ Let's Encrypt certificate जारी नहीं कर सकता: private IPs, .lan जैसे internal-only hostnames, air-gapped networks, और वे services जिन्हें जानबूझकर VPN के पीछे छिपाया गया है। यह निर्णय reachability और naming के बारे में है, security strength के बारे में नहीं — cryptography समान है।

/usr/local/share/ca-certificates में जोड़ने के बाद भी मेरा certificate reject क्यों हो रहा है?

तीन चीजें जांचें। File का extension .crt होना चाहिए — .pem extension को silently skip कर दिया जाता है और update-ca-certificates, 0 added report करता है। Contents PEM text होने चाहिए जो -----BEGIN CERTIFICATE----- से शुरू होता हो, DER binary नहीं। और application को वास्तव में system store का उपयोग करना चाहिए — Linux पर Chrome, Firefox, Python requests, Node.js, और Java, प्रत्येक का अपना private trust store होता है और उन्हें certificate अलग से add करना पड़ता है।

#openssl#tls#self-signed#ubuntu#सुरक्षा