---
title: "cfn-lintとは？CloudFormationテンプレート検証の導入・設定・CI組み込み【v1.57対応】"
url: "https://www.issoh.co.jp/tech/details/10676/"
published: 2026-01-21
updated: 2026-10-30
categories: ["IaC"]
publisher: "株式会社一創"
---

# cfn-lintとは？CloudFormationテンプレート検証の導入・設定・CI組み込み【v1.57対応】

cfn-lintは、AWS CloudFormationのテンプレートをデプロイせずに検証するAWS公式の静的解析ツールです。プロパティ名の打ち間違い、列挙値の誤り、上限を超えた数値、存在しないリソースへの参照といった欠陥を、スタック作成の前にコマンド1本で洗い出せます。本記事は2026-10-06公開のcfn-lint 1.57.2をmacOS上のPython 3.13.14に導入し、実際に流した出力をそのまま載せながら、導入・設定・社内ルールの追加・CI組み込みまでを解説します。

## まとめ：cfn-lint導入の要点

- 導入は`pip install cfn-lint`の1行だが、**対応Pythonは3.10〜3.14**。Python 3.9では半年以上前の1.46.0が黙って入るため、まずPythonの版数を確認する。
- 終了コードは**ビットOR**で決まる（エラー2・警告4・情報8の合算）。CIで警告を許容するなら`--non-zero-exit-code error`を付ける。
- 抑制は`.cfnlintrc.yaml`・テンプレートの`Metadata`・コマンドライン引数の3階層で、後のものが前を上書きする。リソース単位の抑制はMetadataでしか書けない。
- 「SSHを全世界に開けない」「タグを必須にする」といった社内ルールは、1行のカスタムルール（`-z`）か`--override-spec`で足せる。ただし書式を誤ると**エラーにならず黙って何も検査しない**。
- CI組み込みはGitHub Actionsとpre-commitの2通り。pre-commitは対象ファイルの既定が広く、CloudFormation以外のYAMLまで検査してしまう。
- VS Codeで`Unable to start cfn-lint`が出たら、`cfnLint.path`を`~`や環境変数を使わない絶対パスでユーザー設定に書く。

## cfn-lintが実行前に落とせる欠陥の範囲

### 6種類の指摘を1つのテンプレートで再現

cfn-lintは、CloudFormationの**リソースプロバイダースキーマ**（各リソースタイプのプロパティ定義）とルール群を突き合わせて検査します。抽象的な説明よりも出力を見るほうが早いので、欠陥を仕込んだテンプレート（template.yaml）を用意します。

```
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  BucketName:
    Type: String
  UnusedParam:
    Type: String
Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketNames: !Ref BucketName
      VersioningConfiguration:
        Status: Enable
  Func:
    Type: AWS::Lambda::Function
    Properties:
      Role: !GetAtt Bucket.Arn
      Runtime: python3.9
      Handler: index.handler
      Code:
        ZipFile: "def handler(e, c): return 1"
      MemorySize: 100000
Outputs:
  Name:
    Value: !Ref MissingResource
```

これを`cfn-lint template.yaml`に通すと、次の6件が返り、終了コードは6でした（cfn-lint 1.57.2の実行結果をそのまま掲載しています）。

```
W2001 Parameter UnusedParam not used.
template.yaml:5:3

E3002 Additional properties are not allowed ('BucketNames' was unexpected. Did you mean 'BucketName'?)
template.yaml:11:7

E3030 'Enable' is not one of ['Enabled', 'Suspended']
template.yaml:13:9

W2531 Runtime 'python3.9' was deprecated on '2025-12-15'. Creation was disabled on '2027-02-01' and update on '2027-03-03'. Please consider updating to 'python3.14'
template.yaml:18:7

E3034 100000 is greater than the maximum of 32768
template.yaml:22:7

E6101 'MissingResource' is not one of ['BucketName', 'UnusedParam', 'Bucket', 'Func', 'AWS::AccountId', 'AWS::NoValue', 'AWS::NotificationARNs', 'AWS::Partition', 'AWS::Region', 'AWS::StackId', 'AWS::StackName', 'AWS::URLSuffix']
template.yaml:25:5
```

指摘は「ルールID・メッセージ・ファイル名と行:列」の3点セットで、行番号がそのままエディタのジャンプ先になります。E3002はスペルの近い候補まで提示し、W2531はLambdaランタイムの廃止日と代替版を日付付きで返します。参照先のないリソース名（E6101）は、テンプレート内で使える名前の一覧を並べて示します。ルールIDの先頭文字がE（エラー）・W（警告）・I（情報）の重大度で、1.57.2では`cfn-lint -l`で329件のルールが列挙されました（内訳はE243件・W66件・I20件）。1.56.0の323件から6件増えており、ルールは月単位で足されていきます。

### 条件分岐の先まで追う静的解析

cfn-lintの検査は宣言の字面だけでは終わりません。次のテンプレート（billing.yaml）は、本番環境のときだけ`BillingMode`が`PROVISIONED`になるDynamoDBテーブルです。

```
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Env:
    Type: String
    AllowedValues: [dev, prod]
    Default: dev
Conditions:
  IsProd: !Equals [!Ref Env, prod]
Resources:
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: !If [IsProd, PROVISIONED, PAY_PER_REQUEST]
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
```

```
$ cfn-lint billing.yaml
E3639 'ProvisionedThroughput' is a required property
billing.yaml:12:5
```

`PROVISIONED`を選んだ分岐では`ProvisionedThroughput`が必須になるため、dev環境で何度デプロイしても表面化しない欠陥をcfn-lintは事前に指摘します。`--parameters Env=dev`で値を固定して実行しても同じ指摘が出ました。到達しない分岐も検査対象に含める挙動で、条件分岐を多用したテンプレートほど価値が出ます。CloudFormationのテンプレート構文そのものは[AWS CloudFormationの書き方とスタック操作の解説](/tech/details/2927/)で確認してください。

### cfn-nag・CloudFormation Guard・Checkovとの守備範囲の違い

cfn-lintは**仕様どおりに書けているか**を見るツールで、既定のルールにもパスワード設定を確認するW2501などのセキュリティ関連チェックがありますが、組織固有のセキュリティ基準や社内ポリシーを網羅するものではありません。混同しやすい周辺ツールとの関係は次のとおりです。

| ツール       | 主な役割        | 提供元          | 最終更新       |
| --------- | ----------- | ------------ | ---------- |
| cfn-lint  | 仕様・構文・型の検証  | AWS          | 2026-10-16 |
| cfn-guard | ポリシーをDSLで判定 | AWS          | 2026-09-14 |
| Checkov   | セキュリティ設定の検査 | Prisma Cloud | 2026-10-15 |
| cfn\_nag  | セキュリティ設定の検査 | Stelligent   | 2024-08-01 |

最終更新はGitHubリポジトリの最終push日（2026-10-17確認）です。cfn\_nagは2024年8月から更新が止まっており、新しいリソースタイプへの追随は期待しにくい状態にあります。実務では「cfn-lintで仕様違反を落とし、cfn-guardかCheckovでポリシー違反を落とす」という二段構えが素直です。ただし判定が「このプロパティがこの値でないこと」程度で済むなら、後述するcfn-lintのカスタムルールだけで足ります。静的解析そのものの考え方は[静的解析の仕組みとツールの選び方](/tech/details/7475/)にまとめています。なお[AWS CDKで記述したインフラ](/column/details/2892/)を検査したい場合も、`cdk synth`が`cdk.out`配下へ出力するJSONテンプレートを渡せば同じルールで検査できます。

## cfn-lintのインストールと対応Python版

### macOS標準のPythonで入る1.46.0という落とし穴

cfn-lintは**Python 3.10から3.14まで**をサポートします（[PyPIのメタデータ](https://pypi.org/project/cfn-lint/)は`>=3.10,<3.15`）。ここを見落とすと事故になります。macOSに標準で入っているPython 3.9.6の仮想環境で`pip install cfn-lint`を実行すると、エラーにはならず**1.46.0がそのまま導入されます**。1.46.0（2026-03-02公開）のPython要件は`>=3.9`で、pipが3.9で動く最後の版を選ぶためです。

Python 3.9のサポートを切ったのは1.47.0（2026-03-17公開）で、それ以降の約7か月分のルール追加とリソーススキーマ更新が丸ごと抜け落ちます。新しいリソースタイプが「存在しない」と誤検出されたら、まず`cfn-lint --version`を疑ってください。

```
# Python 3.10〜3.14を導入済みの環境（ここでは3.13）
$ python3.13 -m venv .venv
$ source .venv/bin/activate
$ pip install cfn-lint
$ cfn-lint --version
cfn-lint 1.57.2
```

プロジェクト直下の仮想環境に入れておくと、CIとローカルで版数を揃えやすくなります。SARIF形式やJUnit形式で出力する場合は、追加依存を含む`pip install "cfn-lint[sarif]"`や`pip install "cfn-lint[full]"`を指定します（追加依存の名前は`graph`・`junit`・`sarif`・`full`の4つ）。

### WindowsとHomebrew、コンテナでの導入

Windowsではランチャー経由で版数を明示すると確実です。`py -3.13 -m venv .venv`で仮想環境を作り、PowerShellでは`.\.venv\Scripts\Activate.ps1`、コマンドプロンプトでは`.venv\Scripts\activate.bat`で有効化してから`pip install cfn-lint`を実行します。PowerShellで`cfn-lint`が見つからない場合は、仮想環境を有効化せずにグローバルへ入れた結果、`Scripts`ディレクトリがPATHに載っていない可能性があります。未インストールや別のPython環境へのインストールも確認してください。`.venv\Scripts\cfn-lint.exe`のように実行ファイルを直接指定すればPATHの設定を待たずに動かせます。`python -m cfnlint`という呼び出し方は用意されておらず、1.57.2でも`No module named cfnlint.__main__`で失敗します。

macOSではHomebrewの公式formulaも用意されており、2026-10-17時点で1.57.2が配布されています。Homebrew側のPython（python@3.15）に依存して動くため、システムのPython版数に引きずられません。コンテナで動かす場合は、公式リポジトリのDockerfileから`docker build --tag cfn-lint:latest .`でイメージを作り、検査対象のディレクトリを`/data`にマウントして実行する手順が[公式README](https://github.com/aws-cloudformation/cfn-lint)に記載されています。

### 日本語テンプレートの文字コードと読み込みエラー

Windowsで導入した人がつまずきやすいのが文字コードです。古い解説記事では「日本語のコメントを書くと読み込めない」とされていますが、1.57.2のソースではYAMLとJSONのどちらも`encoding="utf-8"`を明示してファイルを開きます。つまり今の失敗条件は「日本語を書いたこと」ではなく、**テンプレートをShift\_JIS（CP932）で保存したこと**です。日本語コメント入りの同じテンプレートを3通りの文字コードで保存して試した結果は次のとおりでした。

| 保存形式              | 結果       | 終了コード |
| ----------------- | -------- | ----- |
| UTF-8             | 指摘なし     | 0     |
| UTF-8（BOM付き）      | 指摘なし     | 0     |
| Shift\_JIS（CP932） | E0000で停止 | 2     |

```
$ cfn-lint ja_sjis.yaml
2026-10-17 11:49:58,134 - cfnlint.decode.decode - ERROR - Cannot read file contents: ja_sjis.yaml
E0000 Cannot read file contents: {filename}
ja_sjis.yaml:1:1
```

E0000の行はファイル名の置き換えが効かず`{filename}`のまま表示されるため、どのファイルが原因かは直前のERROR行か末尾の位置表示で読み取ります。対処はエディタの保存形式をUTF-8に変えるだけで、Windowsの「ワールドワイド言語サポートでUnicode UTF-8を使用」を有効にしたり、日本語コメントを削ったりする必要はありません。

## 終了コードと出力形式の読み方

### 終了コードはビットOR

CIに組み込むうえで最重要の仕様が終了コードです。cfn-lintは検出した重大度ごとに**ビットOR**で値を積み上げます。エラーが2、警告が4、情報が8で、エラーと警告が同時に出れば6になります。実測値は次のとおりでした。

| 検出内容   | 終了コード |
| ------ | ----- |
| 指摘なし   | 0     |
| エラーのみ  | 2     |
| 警告のみ   | 4     |
| エラー＋警告 | 6     |

情報レベルの指摘があればさらに8が加算され、3種類そろうと14になります。「未使用パラメータ（W2001）だけでビルドを落としたくない」という要件は珍しくありません。その場合は`--non-zero-exit-code error`を付けると、エラー以上だけが非ゼロになります。先ほどの6件が出るテンプレートで試すと、終了コードは6から2へ変わりました。値に`none`を指定すると、構文エラー以外は常に0で返ります。

```
$ cfn-lint --non-zero-exit-code error template.yaml; echo "EXIT=$?"
EXIT=2
```

### 出力形式6種類とSARIF・JUnitの追加依存

`-f`で選べる形式は`quiet`・`parseable`・`json`・`junit`・`pretty`・`sarif`の6種類です。GitHubのコード スキャン画面に指摘を出したい場合はSARIFを選びます。ここに落とし穴があり、SARIFとJUnitは**追加依存を入れていないと実行時に失敗します**。素の`pip install cfn-lint`のまま`-f sarif`を指定すると、次の例外で停止しました。

```
ImportError: Missing optional dependencies sarif
```

JUnit形式も同様で、`-f junit`は`Missing optional dependencies junit`で止まります。CIのログでこの行だけを見ると原因を追いにくいため、これらの形式を使うワークフローでは`pip install "cfn-lint[sarif]"`のように追加依存を明示してください。なお情報レベル（Iで始まるルール）は後方互換のため既定で無効で、公式READMEでは`-c I`（`--include-checks I`）で有効化すると案内されています。

## 設定ファイルとMetadataによる抑制の3階層

### .cfnlintrcへの対象と除外の集約

毎回コマンドラインへオプションを並べる代わりに、リポジトリ直下の`.cfnlintrc`・`.cfnlintrc.yaml`・`.cfnlintrc.yml`のいずれかへ設定を書けます（ホームディレクトリの`~/.cfnlintrc`も読まれます）。

```
# .cfnlintrc.yaml
templates:
  - template.yaml
ignore_checks:
  - W2531
regions:
  - ap-northeast-1
```

この状態で引数なしの`cfn-lint`を実行すると、`templates`に書いたファイルが検査され、W2531だけが結果から消えます。`regions`を指定すると、そのリージョンで提供されていないリソースタイプやプロパティを検出できます。CIの設定ファイルにオプションを埋め込むより、リポジトリの設定ファイルへ寄せたほうがローカルとCIの結果が一致します。

### リソース単位の抑制はテンプレートのMetadata

`.cfnlintrc`の`ignore_checks`はテンプレート全体に効くため、「このリソースだけは意図的に仕様外」という抑制には向きません。リソース単位で消すには、対象リソースの`Metadata`へ書きます。

```
Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - E3030
    Properties:
      VersioningConfiguration:
        Status: Enable
```

この記述を加えると、当該リソースのE3030だけが消え、他のリソースのE3030は残ります。抑制の理由をコメントで残せるのも利点です。ただしSAMテンプレートの`AWS::Serverless::*`リソースは、SAM変換の過程でMetadataが失われる場合があると公式READMEに注記されています。

### 適用順は設定ファイル、Metadata、コマンドライン

3つの設定元が競合したときの優先順位は、**設定ファイル → テンプレートのMetadata → コマンドライン引数**の順で後のものが上書きします。CIだけルールを厳しくしたいなら、共通設定を`.cfnlintrc`へ、CI固有の上書きを引数へ置く形が扱いやすくなります。逆に「テンプレートに書いた抑制がCIで効かない」と感じたときは、CI側の引数が上書きしていないかを確認してください。

## 社内ルールを足す：カスタムルールとoverride-spec

既定のルールは「CloudFormationの仕様として正しいか」しか見ません。SSHポートを`0.0.0.0/0`へ開けたセキュリティグループも、タグの無いS3バケットも、仕様上は正しいので素通りします。実際、下のセキュリティグループ（sg.yaml）は既定のままでは指摘0件・終了コード0でした。ここに組織のルールを足す手段が2つ用意されています。

```
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  WebSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: web
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
```

### 1行で書くカスタムルールと書式の落とし穴

1つ目は[カスタムルール](https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/custom%5Frules.md)です。「リソースタイプ・プロパティ・演算子・値・重大度・メッセージ」を1行に並べたテキストファイル（custom\_rules.txt）を作り、`-z`（`--custom-rules`）で渡します。演算子は`EQUALS`・`NOT_EQUALS`・`REGEX_MATCH`・`IN`・`NOT_IN`・大小比較・`IS DEFINED`などが使えます。

```
AWS::EC2::SecurityGroup SecurityGroupIngress.CidrIp NOT_EQUALS "0.0.0.0/0" ERROR "SSHなどを全世界へ開放しない"
```

```
$ cfn-lint sg.yaml -z custom_rules.txt
E9001 SSHなどを全世界へ開放しない
sg.yaml:11:11
```

ルールIDはファイルの行番号から`E9001`のように自動で振られ、終了コードは2になりました。`CidrIp`を`10.0.0.0/16`に変えたテンプレートでは指摘0件です。日本語のメッセージもそのまま表示されます。

注意すべきは書式の誤りが**エラーにならない**点です。公式ドキュメントは書式を`<Property[*]>`と表記していますが、これは記法の説明で、配列の要素を指すつもりで`SecurityGroupIngress[*].CidrIp`と書くと、何も検出せず終了コード0で終わりました。配列はドット区切りのまま書けば要素ごとに検査されます。もう1つ、`AWS::S3::Bucket Tags IS DEFINED WARN "..."`でタグの付け忘れを探すと、`Properties`ブロックを持つバケットは検出されたものの、`Properties`自体を書いていないバケットは見逃されました。ルールを足したら、違反を仕込んだテンプレートで本当に落ちるかを一度確かめてから運用に乗せてください。

### override-specによるスキーマの必須項目追加

2つ目は`--override-spec`（`-o`）で、cfn-lintが持つリソーススキーマ自体にJSON Patchを当てます。[公式の説明](https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/customize%5Fspecifications.md)にあるS3バケットのタグ必須化をそのまま試しました。

```
{
  "Patches": {
    "AWS::S3::Bucket": [
      { "op": "add", "path": "/required", "value": ["Tags"] }
    ]
  }
}
```

この方式では、カスタムルールで見逃した`Properties`の無いバケットも`E3003 'Tags' is a required property`として検出されました。このS3バケットの例では、Properties自体を省略した場合もoverride-specで検出できます。ただし、スキーマ構造の更新でパッチが適用できなくなる場合があるため、cfn-lintの更新時には違反例を再検査してください。使えるリソースタイプを絞る`IncludeResourceTypes`・`ExcludeResourceTypes`もあり、`AWS::EC2::*`のようなワイルドカードが使えます。ここにも落とし穴が2つあります。

- 同じドキュメントの本文は`ExcludedResourceTypes`（edが付く）と書いていますが、このキーで書くと**何も起きず終了コード0**でした。効くのはJSON例と同じ`ExcludeResourceTypes`です。
- 除外したリソースタイプを使うと、`E3006 Resource type 'AWS::EC2::SecurityGroup' does not exist in 'us-east-1'`と「リージョンに存在しない」という文面で報告されます。社内ルールで禁止した結果だと気づけるよう、READMEやPRテンプレートに書き添えておくと混乱を防げます。

### .cfnlintrcによるCIとエディタの設定共有

どちらの設定も`.cfnlintrc.yaml`に書けます。`custom_rules: custom_rules.txt`と`override_spec: override.json`を足して引数なしで実行すると、コマンドラインで渡したときと同じ指摘が出ました。リポジトリに設定を置けばローカル・pre-commit・CIの結果が揃います。複数のアカウントやチームでテンプレートの書き方を統一したい場合、ルールの設計からCIへの組み込みまでを含めて[AWSのインフラ構築支援](/service/system/aws/)として相談いただくこともできます。

## CI組み込み：GitHub Actionsとpre-commit

### GitHub Actionsでの実行とSARIFの取り込み

GitHub Actionsでは、cfn-lintをランナーのPATHへ通すアクションを使う方法と、pipで直接入れる方法があります。前者で広く使われているのが[scottbrenner/cfn-lint-action](https://github.com/scottbrenner/cfn-lint-action)で、最新リリースはv2.8.0（2026-08-14）です。

```
name: Lint CloudFormation Templates
on: [push]
jobs:
  cfn-lint:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v6
      - uses: scottbrenner/cfn-lint-action@v2.8.0
        with:
          version: "[sarif]==1.57.2"
      - run: cfn-lint --non-zero-exit-code error -t ./template.yaml
```

`version`入力を省略すると、最新版が追加依存込みで導入されます。逆に`1.57.2`のような素の版数だけを書くと追加依存が付かないため、SARIF出力を使うワークフローでは`[sarif]==1.57.2`のように**追加依存と版指定をまとめて渡します**（この書式はv2.4.8以降で使えます）。アクションは一時的な仮想環境へcfn-lintを導入するため、後続ステップで`pip install`したプラグインは同じ環境に入りません。プラグインを足す場合は、アクションが返す`cfn_lint_venv`出力のパスを使います。

指摘をコード スキャンのアラートとして残すなら、上の例の最後の`run`ステップを、次の3ステップに置き換えます。アップロードには上の例のように`security-events: write`権限が必要です（非公開リポジトリでは`contents: read`も要ります）。cfn-lintは指摘があると非ゼロで終了するため、生成ステップに`continue-on-error: true`を付けないとアップロードまで進みません。ただし付けただけではジョブが成功扱いになるので、最後のステップで生成ステップの結果（`outcome`）を見てジョブを失敗させます。

```
      - id: lint
        run: cfn-lint --non-zero-exit-code error -f sarif --output-file cfn-lint.sarif -t ./template.yaml
        continue-on-error: true
      - uses: github/codeql-action/upload-sarif@v4
        if: always()
        with:
          sarif_file: cfn-lint.sarif
      - if: steps.lint.outcome == 'failure'
        run: exit 1
```

### pre-commitフックと対象ファイルの絞り込み

コミット前に検査するなら、公式リポジトリが提供するpre-commitフックを使います。用意されているIDは3つで、`cfn-lint`が標準、`cfn-lint-rc`は`.cfnlintrc`の`templates`に任せる版（ファイル名を渡さない）、`cfn-python-lint`は旧名を残した互換IDです。

```
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/aws-cloudformation/cfn-lint
    rev: v1.57.2
    hooks:
      - id: cfn-lint
        files: ^cfn/.*\.(json|yaml|yml)$
```

`files`の指定を省略できない理由があります。フック定義の既定値は`\.(json|yaml|yml)$`で、**リポジトリ内のすべてのYAMLとJSONが検査対象になります**。試しにdocker-compose.ymlをそのまま渡すと、CloudFormationテンプレートではないという指摘が2件返り、終了コードは2でした。

```
$ cfn-lint docker-compose.yml
E1001 'Resources' is a required property
docker-compose.yml:1:1

E1001 Additional properties are not allowed ('services' was unexpected)
docker-compose.yml:1:1
```

「テンプレートとして壊れている場合の指摘を無視する」オプションの`-b`（`--ignore-bad-template`）を付けても、この2件は消えませんでした。テンプレート専用のディレクトリを切って`files`で絞るのが確実です。同じ思想のTerraform向けツールについては[TFLintの導入とCI組み込みの解説](/tech/details/3393/)もあわせて参照してください。

## VS Code拡張で指摘が出ないときの確認順

エディタ上で赤線を出すには、拡張機能「CloudFormation Linter」（識別子は`kddejong.vscode-cfn-lint`、2026-07-09公開の0.27.0）を入れます。この拡張はcfn-lintコマンドを子プロセスとして呼び出す薄い層で、「入れたのに何も出ない」「起動エラーが出る」という詰まり方の原因は次の4つに集約されます。

- **cfn-lint本体が入っていない**。Pythonパッケージの導入は別途必要です。ターミナルで`cfn-lint --version`が通るかを先に確認します。
- **`cfnLint.path`に`~`や環境変数を書いている**。0.27.0は[拡張のソース](https://github.com/aws-cloudformation/cfn-lint-visual-studio-code)上、シェルを介さずに（`shell: false`で）cfn-lintを起動するため、`~/.venv/bin/cfn-lint`や`%USERPROFILE%`は展開されません。このとき出るのが`[cfn-lint] Unable to start cfn-lint (Error: spawn ~/.venv/bin/cfn-lint ENOENT). Is cfn-lint installed correctly?`というエラーで、同じ起動方法をNode.jsで再現すると一字一句同じ文面になりました。絶対パスで書き、空白を含むパスは引用符で囲みます。Windowsの`C:\...\Scripts\cfn-lint.exe`はバックスラッシュのままで構いません。
- **`cfnLint.path`をワークスペース設定に書いている**。拡張の設定説明には「起動する実行ファイルを決めるキーなので、セキュリティ上ユーザー設定またはリモート（マシン）設定で指定し、ワークスペースやフォルダーの設定からは反映しない」と書かれています。仮想環境のcfn-lintを指定したいときは、プロジェクト直下の設定ではなくユーザー設定に書くのが確実です。同じ注意書きが`cfnLint.appendRules`と`cfnLint.ignoreRules`にもあります。
- **ファイルの言語モードがYAMLでもJSONでもない**。拡張の起動条件は`onLanguage:yaml`と`onLanguage:json`で、独自拡張子のテンプレートをPlain Textとして開いていると拡張自体が起動しません。

設定が正しくても保存時まで結果が更新されないことがあるため、判断に迷ったらターミナルで同じファイルを検査して出力を比べてください。手元のIntel Mac上では6件の指摘が出るテンプレート1本の検査に約1.4秒かかりました（マシン性能に左右される値です）。大量のテンプレートを一括検査すると、エディタ側の反映が遅れて見えます。

## SAMテンプレート検証で指摘が40件に膨らむ理由

cfn-lintはAWS SAMのテンプレートにも対応しており、内部でSAM変換を行ってから検査します。[AWS SAMの基本とCLIの使い方](/tech/details/3823/)を押さえていれば、追加の設定なく`cfn-lint template.yaml`がそのまま通ります。ただしSAM特有の読み方が要ります。次の例は、API Gatewayのイベント定義で`Method`を`Methdo`と打ち間違えただけのテンプレートです。

```
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
  Api:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.handler
      Runtime: python3.13
      CodeUri: ./src
      Events:
        Get:
          Type: Api
          Properties:
            Path: /items
            Methdo: GET
```

この綴りの誤りに対して、cfn-lintは**40件**の指摘を返しました（1.57.2でも件数は同じ）。SAMのEventsは「S3・SNS・SQS・Schedule…」という複数のイベント型のいずれかに一致すればよい構造で定義されているため、どの型としても解釈できなくなった時点で**全候補の不一致が個別に報告される**ためです。先頭には「どの定義にも当てはまらない」というE3017が出て、その後に`'Api' is not one of ['S3']`のような一見無関係な行が並びます。これは全候補を順に試した記録です。

```
E3017 {'Type': 'Api', 'Properties': {'Path': '/items', 'Methdo': 'GET'}} is not valid under any of the given schemas
sam.yaml:11:9

E3030 'Api' is not one of ['S3']
（中略）
E3002 Additional properties are not allowed ('Methdo' was unexpected. Did you mean 'Method'?)
sam.yaml:15:13
```

読む順序を変えるだけで解決します。**Did you meanを含む行と、行番号が最も深い位置を指す行から読む**のが定石です。上の例では最後の1行が真因を的確に示していました。件数の多さに驚いて設定で抑制する前に、末尾を確認してください。

## よくある質問

### cfn-lintとcfn-python-lintは別のツールですか？

同じツールの旧称です。PyPIに`cfn-python-lint`というパッケージは存在せず、導入は`pip install cfn-lint`で統一されています。旧名が残っているのはpre-commitのフックIDだけで、既存の設定を壊さないために`cfn-python-lint`というIDが互換用に維持されています。

### pip install cfn-lintで古い版が入るのはなぜですか？

多くはPython 3.9で実行しているためです。cfn-lintは1.47.0（2026-03-17公開）でPython 3.9のサポートを終了しており、pipはエラーを出さずに条件を満たす最後の版である1.46.0を選びます。macOS標準のpython3は3.9系のことがあるため、`python3 --version`と`cfn-lint --version`の両方を確認してください。

### CIで警告は無視してエラーだけ止めたいのですが？

`--non-zero-exit-code error`を付けます。cfn-lintの終了コードは重大度のビットOR（エラー2・警告4・情報8）で決まるため、このオプションを付けないと未使用パラメータの警告だけでもビルドが落ちます。特定のルールだけ黙らせたい場合は`-i W2001`のようにIDを指定するか、`.cfnlintrc`の`ignore_checks`に列挙します。

### cfn-lintでセキュリティの問題も検出できますか？

既定のルールでは限定的です。`0.0.0.0/0`へのSSH開放のような設定は仕様上正しいため指摘されません。「特定のプロパティが特定の値でないこと」「特定のプロパティを必ず書くこと」程度の判定なら、本文で紹介したカスタムルールやoverride-specで足せます。複数のプロパティを組み合わせた条件や、組織全体のポリシーを体系的に管理したい場合はcfn-guardかCheckovを併用してください。パイプラインではcfn-lintを最初に置いて仕様違反で早く落とし、通過したテンプレートだけをポリシー検査へ回す並べ方が無駄がありません。

### cfn-lintはインターネットに接続できない環境でも使えますか？

使えます。リソーススキーマはリージョンごとにパッケージへ同梱されており、プロキシ設定を到達できないアドレスに向けて通信を塞いだ状態でも、冒頭のテンプレートで同じ6件の指摘が出ました。通信が必要になるのは、スキーマを手動で最新化する`--update-specs`（`-u`）を使うときです。閉域網ではスキーマの更新がcfn-lint本体の更新と同時になるため、版数を定期的に上げる運用にしてください。

## 関連記事

- [AWS CloudFormationとは？テンプレートの書き方・スタック操作と更新で消える設定の防ぎ方](/tech/details/2927/)
- [AWS SAMとは？CloudFormationとの違い・CLIの使い方をわかりやすく解説](/tech/details/3823/)
- [TFLintとは？Terraform静的解析の導入・設定・CI組み込み【v0.64.0対応】](/tech/details/3393/)
- [静的解析とは？実行せずに欠陥を見つける仕組みとツールの選び方](/tech/details/7475/)
- [AWS CDK（cdk）とは？仕組み・使い方とCloudFormation・Terraformとの違い](/column/details/2892/)

---

出典: [cfn-lintとは？CloudFormationテンプレート検証の導入・設定・CI組み込み【v1.57対応】](<https://www.issoh.co.jp/tech/details/10676/>)（株式会社一創）
