βWhat is a security group?
<div class="infographic-sgsimple">
<style>
.infographic-sgsimple {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--green: #1e8e5a;
--green-soft: #e2f6ec;
--orange: #e07b17;
--line: #ddd6c9;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem;
max-width: 420px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-sgsimple[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--green: #4fbf87;
--green-soft: #143024;
--orange: #ef9a3e;
--line: #2c313d;
}
.infographic-sgsimple * { box-sizing: border-box; }
.sg-box { border: 2px solid var(--green); border-radius: 16px; padding: 1.4rem 1.4rem 1.6rem; text-align: center; }
.sg-shield { width: 42px; height: 42px; margin: 0 auto 0.4rem; }
.sg-shield path { fill: none; stroke: var(--green); stroke-width: 5; stroke-linejoin: round; }
.sg-name { color: var(--green); font-weight: 800; font-size: 1.25rem; margin: 0; }
.sg-sub { color: var(--ink-soft); font-size: 0.85rem; margin: 0 0 1.4rem; }
.sg-chip-wrap { width: 110px; height: 110px; margin: 0 auto 0.8rem; padding: 8px; box-sizing: border-box; }
.sg-chip-wrap svg { width: 100%; height: 100%; }
.sg-chip-label { font-weight: 700; font-size: 0.95rem; }
.sg-flow { display: flex; justify-content: space-between; margin-top: 1.1rem; }
.sg-flow-item { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.sg-flow-item .arrow { font-size: 1.2rem; }
.sg-flow-item .lbl { font-size: 0.68rem; font-weight: 700; color: var(--ink-soft); }
.sg-flow-item .allow { font-size: 0.62rem; font-weight: 800; color: var(--green);
border: 1px solid var(--green); border-radius: 999px; padding: 0.05rem 0.4rem; }
.sg-note { display: flex; align-items: center; gap: 0.8rem; background: var(--paper-alt); border: 1px solid var(--line);
border-radius: 12px; padding: 0.9rem 1rem; margin-top: 1.25rem; font-size: 0.88rem; color: var(--ink); }
.sg-note .icon { width: 26px; height: 26px; flex-shrink: 0; }
.sg-note .icon path { fill: none; stroke: var(--green); stroke-width: 5; stroke-linejoin: round; }
</style>
<div class="sg-box">
<svg class="sg-shield" viewBox="0 0 48 54">
<path d="M24 3 L44 11 V26 C44 39 35 47 24 51 C13 47 4 39 4 26 V11 Z"/>
</svg>
<p class="sg-name">Security Group</p>
<p class="sg-sub">(Virtual Firewall)</p>
<div class="sg-chip-wrap">
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80" rx="14" fill="var(--orange)"/>
<rect x="34" y="34" width="32" height="32" rx="3" fill="none" stroke="#ffffff" stroke-width="3.2"/>
<g stroke="#ffffff" stroke-width="3.2" stroke-linecap="round">
<line x1="42" y1="10" x2="42" y2="20"/>
<line x1="50" y1="10" x2="50" y2="20"/>
<line x1="58" y1="10" x2="58" y2="20"/>
<line x1="42" y1="80" x2="42" y2="90"/>
<line x1="50" y1="80" x2="50" y2="90"/>
<line x1="58" y1="80" x2="58" y2="90"/>
<line x1="10" y1="42" x2="20" y2="42"/>
<line x1="10" y1="50" x2="20" y2="50"/>
<line x1="10" y1="58" x2="20" y2="58"/>
<line x1="80" y1="42" x2="90" y2="42"/>
<line x1="80" y1="50" x2="90" y2="50"/>
<line x1="80" y1="58" x2="90" y2="58"/>
</g>
</svg>
</div>
<div class="sg-chip-label">EC2 Instance</div>
<div class="sg-flow">
<div class="sg-flow-item">
<span class="arrow">π₯</span>
<span class="lbl">Inbound</span>
<span class="allow">ALLOW</span>
</div>
<div class="sg-flow-item">
<span class="arrow">π€</span>
<span class="lbl">Outbound</span>
<span class="allow">ALLOW</span>
</div>
</div>
</div>
<div class="sg-note">
<svg class="icon" viewBox="0 0 48 54">
<path d="M24 3 L44 11 V26 C44 39 35 47 24 51 C13 47 4 39 4 26 V11 Z"/>
</svg>
<span>A security group is a virtual firewall attached to your EC2 instance.</span>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-sgsimple')
|| document.querySelector('.infographic-sgsimple');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});A virtual firewall attached to your instance. It controls two directions of traffic:
π₯ Inbound β what's allowed to reach the instance
π€ Outbound β what's allowed to leave it
Key thing to know: security groups only have ALLOW rules. There's no "deny" β anything not explicitly allowed is blocked by default.
π€ What a rule can match against
π IP addresses β e.g. your laptop's public IP
π Other security groups β rules can reference another security group instead of an IP, letting resources trust each other directly
πββοΈ How it plays out
<div class="infographic-sganim">
<style>
.infographic-sganim {
--ink: #1a1a1a;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--blue: #2f6fb0;
--orange: #e07b17;
--green: #1e8e5a;
--line: #ddd6c9;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 0;
max-width: 640px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-sganim[data-theme="dark"] {
--ink: #e8e6e1;
--paper: #121212;
--paper-alt: #1e1e26;
--blue: #5a94d6;
--orange: #ef9a3e;
--green: #4fbf87;
--line: #2c313d;
}
.infographic-sganim * { box-sizing: border-box; }
.sga-stage { position: relative; height: 150px; border: 1.5px solid var(--line); border-radius: 12px;
background: var(--paper-alt); overflow: hidden; }
.sga-globe { position: absolute; left: 8%; top: 50%; transform: translate(-50%, -50%); font-size: 2rem;
animation: sga-globe-pulse 6s ease-in-out infinite; }
@keyframes sga-globe-pulse {
0%, 4%, 94%, 100% { transform: translate(-50%, -50%) scale(1.15); }
10%, 88% { transform: translate(-50%, -50%) scale(1); }
}
.sga-bar { position: absolute; left: 47%; top: 50%; transform: translate(-50%, -50%); width: 34px; height: 96px;
background: var(--blue); border-radius: 6px; display: flex; align-items: center; justify-content: center; }
.sga-bar svg { width: 18px; height: 18px; }
.sga-bar path { fill: none; stroke: #fff; stroke-width: 6; stroke-linejoin: round; }
.sga-check { position: absolute; left: 47%; top: 22%; transform: translate(-50%, -50%); font-size: 1rem;
opacity: 0; animation: sga-check-flash 6s ease-in-out infinite; }
@keyframes sga-check-flash {
0%, 24%, 34%, 100% { opacity: 0; }
27%, 31% { opacity: 1; }
}
.sga-chip { position: absolute; left: 84%; top: 50%; transform: translate(-50%, -50%); width: 62px; height: 62px;
animation: sga-chip-pulse 6s ease-in-out infinite; }
.sga-chip svg { width: 100%; height: 100%; }
@keyframes sga-chip-pulse {
0%, 46%, 56%, 100% { transform: translate(-50%, -50%) scale(1); }
51% { transform: translate(-50%, -50%) scale(1.12); }
}
.sga-packet { position: absolute; top: 50%; font-size: 1.2rem; transform: translate(-50%, -50%);
animation: sga-packet-move 6s linear infinite; }
@keyframes sga-packet-move {
0% { left: 8%; opacity: 0; }
4% { opacity: 1; }
22% { left: 45%; opacity: 1; }
26% { left: 45%; opacity: 0; }
30% { left: 55%; opacity: 0; }
34% { opacity: 1; }
48% { left: 82%; opacity: 1; }
53% { left: 82%; opacity: 0; }
57% { left: 82%; opacity: 0; }
61% { opacity: 1; }
75% { left: 55%; opacity: 1; }
79% { left: 55%; opacity: 0; }
83% { left: 45%; opacity: 0; }
87% { opacity: 1; }
100% { left: 8%; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.infographic-sganim * { animation: none !important; }
}
</style>
<div class="sga-stage">
<span class="sga-globe">π</span>
<div class="sga-bar">
<svg viewBox="0 0 48 54"><path d="M24 3 L44 11 V26 C44 39 35 47 24 51 C13 47 4 39 4 26 V11 Z"/></svg>
</div>
<span class="sga-check">β
</span>
<div class="sga-chip">
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80" rx="14" fill="var(--orange)"/>
<rect x="34" y="34" width="32" height="32" rx="3" fill="none" stroke="#ffffff" stroke-width="3.5"/>
<g stroke="#ffffff" stroke-width="3.5" stroke-linecap="round">
<line x1="42" y1="10" x2="42" y2="20"/><line x1="50" y1="10" x2="50" y2="20"/><line x1="58" y1="10" x2="58" y2="20"/>
<line x1="42" y1="80" x2="42" y2="90"/><line x1="50" y1="80" x2="50" y2="90"/><line x1="58" y1="80" x2="58" y2="90"/>
<line x1="10" y1="42" x2="20" y2="42"/><line x1="10" y1="50" x2="20" y2="50"/><line x1="10" y1="58" x2="20" y2="58"/>
<line x1="80" y1="42" x2="90" y2="42"/><line x1="80" y1="50" x2="90" y2="50"/><line x1="80" y1="58" x2="90" y2="58"/>
</g>
</svg>
</div>
<span class="sga-packet">π¨</span>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-sganim')
|| document.querySelector('.infographic-sganim');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});Picture yourself on your laptop, out on the public internet, trying to reach your EC2 instance:
That instance sits behind a security group
The security group holds a set of rules
Those rules decide, traffic by traffic: does this get to come in? Does this get to go out?
If there's no rule allowing it, the traffic simply doesn't get through.
π₯ Security Groups β Deeper Dive
<div class="infographic-sgdiagram">
<style>
.infographic-sgdiagram {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--blue: #2f6fb0;
--orange: #d97706;
--orange-soft:#fdecd2;
--line: #ddd6c9;
--ok: #1e8e5a;
--ok-soft: #e2f6ec;
--bad: #c0392b;
--bad-soft: #fbe9e7;
--mono-font: 'SF Mono', Menlo, Consolas, monospace;
--display-font: Georgia, 'Iowan Old Style', serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 780px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-sgdiagram[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--blue: #5a94d6;
--orange: #f0a94e;
--orange-soft:#3a2b12;
--line: #2c313d;
--ok: #4fbf87;
--ok-soft: #143024;
--bad: #e57373;
--bad-soft: #3a2222;
}
.infographic-sgdiagram * { box-sizing: border-box; }
.sgd-section-title { font-family: var(--display-font); font-size: 1.05rem; margin: 0 0 0.7rem;
display: flex; align-items: center; gap: 0.4rem; }
.sgd-section { margin-bottom: 1.6rem; }
.sgd-row { display: flex; align-items: center; gap: 0; margin-bottom: 0.7rem; flex-wrap: nowrap; }
@media (max-width: 640px) { .sgd-row { flex-wrap: wrap; row-gap: 0.5rem; } }
.sgd-node { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; opacity: 0.32;
transition: opacity 0.3s; flex-shrink: 0; width: 92px; }
.sgd-node.is-lit { opacity: 1; }
.sgd-node .box { width: 48px; height: 48px; border-radius: 10px; border: 1.5px solid var(--line);
display: flex; align-items: center; justify-content: center; font-size: 1.3rem; background: var(--paper-alt);
transition: border-color 0.25s, background 0.25s, transform 0.25s; }
.sgd-node.is-ok .box { border-color: var(--ok); background: var(--ok-soft); }
.sgd-node.is-bad .box { border-color: var(--bad); background: var(--bad-soft); animation: sgd-shake 0.4s; }
.sgd-node[data-role="filter"] .box { border-color: var(--orange); }
.sgd-node[data-role="filter"].is-lit .box { background: var(--orange-soft); }
.sgd-node .lbl { font-size: 0.66rem; font-weight: 700; color: var(--ink-soft); text-align: center; line-height: 1.2; }
@keyframes sgd-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-4px); }
40% { transform: translateX(4px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
.sgd-seg-wrap { display: flex; flex-direction: column; align-items: center; flex: 1 1 40px; min-width: 30px; }
.sgd-seg { width: 100%; height: 2px; background: var(--line); transition: background 0.25s; }
.sgd-seg.is-lit { background: var(--ok); }
.sgd-seg.is-cut { background: var(--bad); }
.sgd-port { font-size: 0.6rem; font-family: var(--mono-font); font-weight: 700; color: var(--ink-soft); margin-top: 2px; }
.sgd-play { flex-shrink: 0; font: inherit; font-size: 0.76rem; font-weight: 700; padding: 0.4rem 0.75rem;
border-radius: 999px; cursor: pointer; margin-left: 0.6rem; }
.sgd-play.good { border: 1.5px solid var(--ok); color: var(--ok); background: var(--paper); }
.sgd-play.bad { border: 1.5px solid var(--bad); color: var(--bad); background: var(--paper); }
.sgd-play:hover { opacity: 0.8; }
.sgd-play:disabled { opacity: 0.4; cursor: default; }
@media (max-width: 640px) {
.sgd-play { margin-left: 0; width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
.infographic-sgdiagram * { animation: none !important; transition-duration: 0.001ms !important; }
}
</style>
<div class="sgd-section">
<p class="sgd-section-title">π₯ Inbound β Security Group 1</p>
<div class="sgd-row" data-flow="allow">
<div class="sgd-node" data-n="src"><div class="box">π»</div><span class="lbl">Your Computer</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="1"></div><span class="sgd-port">port 22</span></div>
<div class="sgd-node" data-n="filter" data-role="filter"><div class="box">π‘οΈ</div><span class="lbl">Security Group</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="2"></div></div>
<div class="sgd-node" data-n="ec2"><div class="box">π₯οΈ</div><span class="lbl">EC2 Instance</span></div>
<button class="sgd-play good" data-play="allow">βΆ Send</button>
</div>
<div class="sgd-row" data-flow="deny">
<div class="sgd-node" data-n="src"><div class="box">π»</div><span class="lbl">Other Computer</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="1"></div><span class="sgd-port">port 22</span></div>
<div class="sgd-node" data-n="filter" data-role="filter"><div class="box">π‘οΈ</div><span class="lbl">Security Group</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="2"></div></div>
<div class="sgd-node" data-n="ec2"><div class="box">π₯οΈ</div><span class="lbl">EC2 Instance</span></div>
<button class="sgd-play bad" data-play="deny">βΆ Send</button>
</div>
</div>
<div class="sgd-section">
<p class="sgd-section-title">π€ Outbound β Security Group 1</p>
<div class="sgd-row" data-flow="allow">
<div class="sgd-node" data-n="ec2"><div class="box">π₯οΈ</div><span class="lbl">EC2 Instance</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="1"></div></div>
<div class="sgd-node" data-n="filter" data-role="filter"><div class="box">π‘οΈ</div><span class="lbl">Security Group</span></div>
<div class="sgd-seg-wrap"><div class="sgd-seg" data-seg="2"></div><span class="sgd-port">any port</span></div>
<div class="sgd-node" data-n="www"><div class="box">π</div><span class="lbl">WWW</span></div>
<button class="sgd-play good" data-play="allow">βΆ Send</button>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-sgdiagram')
|| document.querySelector('.infographic-sgdiagram');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
wrapper.querySelectorAll('.sgd-row').forEach(row => {
const allowed = row.dataset.flow === 'allow';
const nodes = Array.from(row.querySelectorAll('.sgd-node'));
const segs = Array.from(row.querySelectorAll('.sgd-seg'));
const btn = row.querySelector('.sgd-play');
let running = false;
function reset() {
nodes.forEach(n => n.classList.remove('is-lit', 'is-ok', 'is-bad'));
segs.forEach(s => s.classList.remove('is-lit', 'is-cut'));
}
btn.addEventListener('click', () => {
if (running) return;
running = true;
btn.disabled = true;
reset();
const steps = [];
steps.push(() => nodes[0].classList.add('is-lit'));
steps.push(() => segs[0].classList.add('is-lit'));
steps.push(() => nodes[1].classList.add('is-lit'));
if (allowed) {
steps.push(() => segs[1].classList.add('is-lit'));
steps.push(() => { nodes[1].classList.add('is-ok'); nodes[2].classList.add('is-lit', 'is-ok'); });
steps.push(() => { running = false; btn.disabled = false; });
} else {
steps.push(() => { nodes[1].classList.add('is-bad'); segs[1].classList.add('is-cut'); });
steps.push(() => { running = false; btn.disabled = false; });
}
steps.forEach((fn, i) => setTimeout(fn, i * 500));
});
});
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});Security groups act as a firewall on EC2 instances. They regulate:
Access to specific ports
Authorized IP ranges (IPv4 and IPv6)
Inbound traffic β who can reach the instance
Outbound traffic β where the instance can reach out to
Remember it as: Inbound = who can enter Β· Outbound = where EC2 can go.
Control who's allowed to connect to your instance.
β Matching IP + port β connection succeeds
β IP not covered by any rule β connection is blocked (times out, doesn't get an error back)
Control where the instance is allowed to connect out to.
β By default, all outbound traffic is allowed β nothing to configure unless you want to lock it down
Example rule set
Type | Protocol | Port Range | Source | Description |
|---|---|---|---|---|
HTTP | TCP | 80 | 0.0.0.0/0 | test http page |
SSH | TCP | 22 | 122.149.196.85/32 | β |
Custom TCP Rule | TCP | 4567 | 0.0.0.0/0 | java app |
Each rule really just says: this port, this protocol, allowed from this source.
Worth noting:
0.0.0.0/0means "anywhere on the internet" β wide openThe SSH rule is scoped to one exact IP (
/32) β much tighter, and the pattern worth following for anything admin-relatedHTTP and the custom app port here are both open to the world, which makes sense for a public web service but would be a mistake for something like SSH
π₯ Security Groups β Good to Know
Reusable across instances. One security group isn't tied to a single EC2 instance β you can attach the same one to several instances that should share the same access rules.
Scoped to a region + VPC (Virtual Private Cloud). A security group only exists within the region and VPC it was created in β you can't attach it to an instance somewhere else.
It lives outside the instance, not inside it. The filtering happens before traffic ever touches the EC2 instance. If a security group blocks something, the instance itself never sees the attempt β there's nothing to log or debug on the instance side, because it never arrived.
π‘ Best practice: give SSH its own dedicated security group. Instead of bundling SSH access into the same group as your app's HTTP/HTTPS rules, keep it separate. That way you can tighten or loosen admin access without touching the rules your application actually depends on.
A "timed out" connection almost always means a security group problem. The request just sits there with no response β because the security group silently dropped it before it ever reached your app.
A "connection refused" error usually means an application problem, not a firewall one. The traffic did get through the security group β it reached the instance β but nothing was listening on that port, either because the app crashed or was never started.
Inbound traffic is blocked by default. A brand-new security group lets nothing in until you explicitly add a rule allowing it.
Outbound traffic is allowed by default. Unlike inbound, a new security group lets your instance freely reach out anywhere, unless you deliberately restrict it.
The mental shortcut: timeout β check the security group. Connection refused β check the application.
π Referencing Other Security Groups
<div class="infographic-sgref2">
<style>
.infographic-sgref2 {
--ink: #1a1a1a;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--blue: #3f7fc1;
--orange: #eab04a;
--gray: #9a9a9a;
--line: #ddd6c9;
--ok: #3a9d5c;
--bad: #c0392b;
--mono-font: 'SF Mono', Menlo, Consolas, monospace;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 860px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-sgref2[data-theme="dark"] {
--ink: #e8e6e1;
--paper: #121212;
--paper-alt: #1e1e26;
--blue: #5d94d1;
--orange: #d9a24e;
--gray: #8a8a8a;
--line: #2c313d;
--ok: #4fbf87;
--bad: #e57373;
}
.infographic-sgref2 * { box-sizing: border-box; min-width: 0; }
.sr2-wrap { display: flex; align-items: stretch; gap: 0.7rem; }
@media (max-width: 760px) { .sr2-wrap { flex-direction: column; } }
/* Left: primary instance + filter, side by side, full height on desktop */
.sr2-left { display: flex; gap: 0.7rem; flex: 0 0 auto; }
@media (max-width: 760px) { .sr2-left { flex-direction: row; } }
.sr2-ec2main, .sr2-filter { border-radius: 10px; padding: 1rem 0.8rem; display: flex; flex-direction: column;
align-items: center; justify-content: center; text-align: center; width: 130px; flex-shrink: 0; }
@media (max-width: 480px) { .sr2-ec2main, .sr2-filter { width: auto; flex: 1 1 0; } }
.sr2-ec2main { background: var(--blue); color: #fff; }
.sr2-ec2main .name { font-weight: 800; font-size: 0.95rem; line-height: 1.3; }
.sr2-ec2main .ip { font-weight: 400; font-size: 0.72rem; opacity: 0.85; margin-top: 0.3rem; }
.sr2-filter { background: var(--orange); color: #4a3200; }
.sr2-filter .name { font-weight: 800; font-size: 0.9rem; line-height: 1.3; }
.sr2-filter .authlist { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.6rem; }
.sr2-filter .chip { background: rgba(255,255,255,0.55); border-radius: 999px; padding: 0.2rem 0.6rem;
font-weight: 700; font-size: 0.72rem; }
/* Right: rows */
.sr2-rows { display: flex; flex-direction: column; gap: 0.6rem; flex: 1 1 auto; justify-content: space-between; }
.sr2-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; justify-content: center;
border: 1px solid var(--line); border-radius: 10px; padding: 0.6rem 0.7rem; }
@media (max-width: 760px) { .sr2-row { justify-content: center; } }
.sr2-target { flex: 1 1 130px; max-width: 170px; background: var(--blue); color: #fff; border-radius: 6px;
padding: 0.6rem 0.5rem; text-align: center; }
.sr2-target .name { font-weight: 700; font-size: 0.78rem; line-height: 1.3; overflow-wrap: break-word; }
.sr2-target .ip { font-weight: 400; font-size: 0.66rem; opacity: 0.85; margin-top: 0.15rem; }
.sr2-badge { flex: 1 1 110px; max-width: 150px; border-radius: 6px; padding: 0.6rem 0.5rem; text-align: center;
font-weight: 700; font-size: 0.76rem; line-height: 1.3; color: #fff; }
.sr2-badge.sg1 { background: var(--orange); color: #4a3200; }
.sr2-badge.sg2 { background: #d4a017; color: #3a2900; }
.sr2-badge.sg3 { background: var(--gray); }
.sr2-badge .sub { font-weight: 400; font-size: 0.64rem; opacity: 0.85; }
.sr2-line-wrap { flex: 1 1 90px; display: flex; flex-direction: column; align-items: stretch; }
.sr2-portlbl { font-family: var(--mono-font); font-size: 0.68rem; font-weight: 700; text-align: center;
color: var(--ink); opacity: 0.6; margin-bottom: 3px; }
.sr2-line { height: 6px; background: var(--line); border-radius: 3px; position: relative; transition: background 0.25s; }
.sr2-line::after { content: ""; position: absolute; left: -2px; top: 50%; transform: translateY(-50%);
border-style: solid; border-width: 6px 9px 6px 0; border-color: transparent var(--line) transparent transparent;
transition: border-color 0.25s; }
.sr2-line.is-ok { background: var(--ok); }
.sr2-line.is-ok::after { border-right-color: var(--ok); }
.sr2-line.is-bad { background: var(--bad); }
.sr2-line.is-bad::after { border-right-color: var(--bad); }
.sr2-play { flex: 0 0 auto; font: inherit; font-size: 0.74rem; font-weight: 700; padding: 0.45rem 0.85rem;
border-radius: 999px; cursor: pointer; border: 1.5px solid var(--line); background: var(--paper); color: var(--ink); }
.sr2-play:hover { border-color: var(--blue); }
.sr2-play:disabled { opacity: 0.4; cursor: default; }
@media (prefers-reduced-motion: reduce) {
.infographic-sgref2 * { transition-duration: 0.001ms !important; }
}
</style>
<div class="sr2-wrap">
<div class="sr2-left">
<div class="sr2-ec2main">
<div class="name">Our Primary Instance</div>
<div class="ip">IP XX.XX.XX.XX</div>
</div>
<div class="sr2-filter">
<div class="name">Security Group 1<br>Inbound</div>
<div class="authlist">
<span class="chip">β SG1</span>
<span class="chip">β SG2</span>
</div>
</div>
</div>
<div class="sr2-rows">
<div class="sr2-row" data-flow="allow">
<div class="sr2-target"><div class="name">External Instance A</div><div class="ip">IP XX.XX.XX.XX</div></div>
<div class="sr2-badge sg2">Security Group 2<div class="sub">(attached)</div></div>
<div class="sr2-line-wrap"><span class="sr2-portlbl">Port 123</span><div class="sr2-line" data-line="1"></div></div>
<button class="sr2-play" data-play="allow">βΆ Send</button>
</div>
<div class="sr2-row" data-flow="allow">
<div class="sr2-target"><div class="name">External Instance B</div><div class="ip">IP XX.XX.XX.XX</div></div>
<div class="sr2-badge sg1">Security Group 1<div class="sub">(attached)</div></div>
<div class="sr2-line-wrap"><span class="sr2-portlbl">Port 123</span><div class="sr2-line" data-line="1"></div></div>
<button class="sr2-play" data-play="allow">βΆ Send</button>
</div>
<div class="sr2-row" data-flow="deny">
<div class="sr2-target"><div class="name">External Instance C</div><div class="ip">IP XX.XX.XX.XX</div></div>
<div class="sr2-badge sg3">Security Group 3<div class="sub">(attached)</div></div>
<div class="sr2-line-wrap"><span class="sr2-portlbl">Port 123</span><div class="sr2-line" data-line="1"></div></div>
<button class="sr2-play" data-play="deny">βΆ Send</button>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-sgref2')
|| document.querySelector('.infographic-sgref2');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const mainEc2 = wrapper.querySelector('.sr2-ec2main');
wrapper.querySelectorAll('.sr2-row').forEach(row => {
const allowed = row.dataset.flow === 'allow';
const line = row.querySelector('.sr2-line');
const btn = row.querySelector('.sr2-play');
let running = false;
btn.addEventListener('click', () => {
if (running) return;
running = true;
btn.disabled = true;
line.classList.remove('is-ok', 'is-bad');
mainEc2.style.transform = '';
setTimeout(() => {
line.classList.add(allowed ? 'is-ok' : 'is-bad');
}, 120);
setTimeout(() => {
if (allowed) {
mainEc2.style.transition = 'transform 0.2s';
mainEc2.style.transform = 'scale(1.05)';
setTimeout(() => { mainEc2.style.transform = ''; }, 260);
}
running = false;
btn.disabled = false;
}, 650);
});
});
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});A small advanced feature β genuinely useful once load balancers come into play β that lets you authorize traffic by security group instead of by IP address.
π€ The setup
Our Primary Instance has Security Group 1 attached. Its inbound rule says: allow traffic from anything wearing Security Group 1 or Security Group 2 (External Groups)
π€ What that means for the other instances
External Instance A wears Security Group 2 β it's on the trusted list, so it's allowed to connect in
External Instance B wears Security Group 1 β same group as the primary instance's own, also trusted, allowed to connect in
External Instance C wears Security Group 3 β not on the trusted list, so its connection attempt gets denied
Each of these is an external instance trying to reach our primary instance β the badge they're wearing determines whether that connection is let through.
π§ Why this is worth knowing
None of this cares about IP addresses. Access is decided entirely by which security group an instance is wearing β so as long as an instance carries an authorized group, it gets through, no matter what its actual IP happens to be.
That's the real payoff: you stop babysitting IPs. Instances get relaunched, replaced, scaled up and down constantly in AWS β and this pattern doesn't break when that happens. It's exactly why this shows up so often with load balancers, whose IPs were never fixed to begin with.
The one-picture summary: Security Group 1's inbound rule lists which other security groups it trusts. Wear one of those, and you're in. Wear anything else, and you're not β regardless of IP.
π Important Ports (Exam + Real World)
Port | Protocol | Use Case |
|---|---|---|
22 | SSH | Linux access |
21 | FTP | File transfer (insecure) |
22 | SFTP | Secure file transfer |
80 | HTTP | Unsecured web |
443 | HTTPS | Secured web |
3389 | RDP | Windows access |